map: hero fixes

This commit is contained in:
Thibault Deckers 2024-10-24 20:53:29 +02:00
parent 9aeb0a1fc3
commit d96f9768f9
8 changed files with 275 additions and 191 deletions

View file

@ -142,28 +142,28 @@ class AvesAppBar extends StatelessWidget {
static Widget _flightShuttleBuilder( static Widget _flightShuttleBuilder(
BuildContext flightContext, BuildContext flightContext,
Animation<double> animation, Animation<double> animation,
HeroFlightDirection direction, HeroFlightDirection flightDirection,
BuildContext fromHero, BuildContext fromHeroContext,
BuildContext toHero, BuildContext toHeroContext,
) { ) {
final pushing = direction == HeroFlightDirection.push; final pushing = flightDirection == HeroFlightDirection.push;
Widget popBuilder(context, child) => Opacity(opacity: 1 - animation.value, child: child); Widget popBuilder(context, child) => Opacity(opacity: 1 - animation.value, child: child);
Widget pushBuilder(context, child) => Opacity(opacity: animation.value, child: child); Widget pushBuilder(context, child) => Opacity(opacity: animation.value, child: child);
return Material( return Material(
type: MaterialType.transparency, type: MaterialType.transparency,
child: DefaultTextStyle( child: DefaultTextStyle(
style: DefaultTextStyle.of(toHero).style, style: DefaultTextStyle.of(toHeroContext).style,
child: Stack( child: Stack(
children: [ children: [
AnimatedBuilder( AnimatedBuilder(
animation: animation, animation: animation,
builder: pushing ? popBuilder : pushBuilder, builder: pushing ? popBuilder : pushBuilder,
child: fromHero.widget, child: fromHeroContext.widget,
), ),
AnimatedBuilder( AnimatedBuilder(
animation: animation, animation: animation,
builder: pushing ? pushBuilder : popBuilder, builder: pushing ? pushBuilder : popBuilder,
child: toHero.widget, child: toHeroContext.widget,
), ),
], ],
), ),

View file

@ -1,3 +1,4 @@
import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/text.dart'; import 'package:aves/theme/text.dart';
import 'package:aves/widgets/aves_app.dart'; import 'package:aves/widgets/aves_app.dart';
import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/extensions/build_context.dart';
@ -5,6 +6,7 @@ import 'package:aves/widgets/viewer/info/common.dart';
import 'package:aves_map/aves_map.dart'; import 'package:aves_map/aves_map.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:provider/provider.dart';
class Attribution extends StatelessWidget { class Attribution extends StatelessWidget {
final EntryMapStyle? style; final EntryMapStyle? style;
@ -33,17 +35,40 @@ class Attribution extends StatelessWidget {
Widget _buildOsmAttributionMarkdown(BuildContext context, String data) { Widget _buildOsmAttributionMarkdown(BuildContext context, String data) {
final theme = Theme.of(context); final theme = Theme.of(context);
Widget child = MarkdownBody(
data: '${context.l10n.mapAttributionOsmData}${AText.separator}$data',
selectable: true,
styleSheet: MarkdownStyleSheet(
a: TextStyle(color: theme.colorScheme.primary),
p: theme.textTheme.bodySmall!.merge(const TextStyle(fontSize: InfoRowGroup.fontSize)),
),
onTapLink: (text, href, title) => AvesApp.launchUrl(href),
);
final animate = context.select<Settings, bool>((v) => v.animate);
if (animate) {
child = Hero(
tag: 'map-attribution',
flightShuttleBuilder: (flightContext, animation, flightDirection, fromHeroContext, toHeroContext) {
return DefaultTextStyle(
style: DefaultTextStyle.of(toHeroContext).style,
child: MediaQuery.removeViewPadding(
context: context,
removeLeft: true,
removeTop: true,
removeRight: true,
removeBottom: true,
child: toHeroContext.widget,
),
);
},
child: child,
);
}
return Padding( return Padding(
padding: const EdgeInsets.only(top: 4), padding: const EdgeInsets.only(top: 4),
child: MarkdownBody( child: child,
data: '${context.l10n.mapAttributionOsmData}${AText.separator}$data',
selectable: true,
styleSheet: MarkdownStyleSheet(
a: TextStyle(color: theme.colorScheme.primary),
p: theme.textTheme.bodySmall!.merge(const TextStyle(fontSize: InfoRowGroup.fontSize)),
),
onTapLink: (text, href, title) => AvesApp.launchUrl(href),
),
); );
} }
} }

View file

@ -9,6 +9,7 @@ import 'package:aves/widgets/common/map/buttons/button.dart';
import 'package:aves/widgets/common/map/buttons/coordinate_filter.dart'; import 'package:aves/widgets/common/map/buttons/coordinate_filter.dart';
import 'package:aves/widgets/common/map/compass.dart'; import 'package:aves/widgets/common/map/compass.dart';
import 'package:aves/widgets/common/map/map_action_delegate.dart'; import 'package:aves/widgets/common/map/map_action_delegate.dart';
import 'package:aves/widgets/common/providers/map_theme_provider.dart';
import 'package:aves_map/aves_map.dart'; import 'package:aves_map/aves_map.dart';
import 'package:aves_model/aves_model.dart'; import 'package:aves_model/aves_model.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -52,46 +53,12 @@ class _MapButtonPanelState extends State<MapButtonPanel> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final iconTheme = IconTheme.of(context);
final iconSize = Size.square(iconTheme.size!);
Widget? navigationButton;
switch (context.select<MapThemeData, MapNavigationButton>((v) => v.navigationButton)) {
case MapNavigationButton.back:
if (!settings.useTvLayout) {
navigationButton = MapOverlayButton.icon(
icon: const BackButtonIcon(),
onPressed: () => Navigator.maybeOf(context)?.pop(),
tooltip: MaterialLocalizations.of(context).backButtonTooltip,
);
}
case MapNavigationButton.close:
navigationButton = MapOverlayButton.icon(
icon: const CloseButtonIcon(),
onPressed: SystemNavigator.pop,
tooltip: MaterialLocalizations.of(context).closeButtonTooltip,
);
case MapNavigationButton.map:
final _openMapPage = widget.openMapPage;
if (_openMapPage != null) {
navigationButton = MapOverlayButton.icon(
icon: const Icon(AIcons.showFullscreenCorners),
onPressed: () => _openMapPage.call(context),
tooltip: context.l10n.openMapPageTooltip,
);
}
case MapNavigationButton.none:
break;
}
final showCoordinateFilter = context.select<MapThemeData, bool>((v) => v.showCoordinateFilter); final showCoordinateFilter = context.select<MapThemeData, bool>((v) => v.showCoordinateFilter);
final visualDensity = context.select<MapThemeData, VisualDensity>((v) => v.visualDensity); final visualDensity = context.select<MapThemeData, VisualDensity>((v) => v.visualDensity);
final double padding = 8 + visualDensity.horizontal * 2; final double padding = 8 + visualDensity.horizontal * 2;
final actions = [ Widget? topLeftButton = _buildNavigationButton(context);
MapAction.openMapApp, Widget? topRightButton = _buildTopRightButton(context);
MapAction.addShortcut,
].where((action) => _actionDelegate.isVisible(context, action)).toList();
return Positioned.fill( return Positioned.fill(
child: TooltipTheme( child: TooltipTheme(
@ -113,38 +80,11 @@ class _MapButtonPanelState extends State<MapButtonPanel> {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
if (navigationButton != null) ...[ if (topLeftButton != null) ...[
navigationButton, topLeftButton,
SizedBox(height: padding), SizedBox(height: padding),
], ],
ValueListenableBuilder<ZoomedBounds>( _buildCompass(context),
valueListenable: widget.boundsNotifier,
builder: (context, bounds, child) {
final degrees = bounds.rotation;
final opacity = degrees == 0 ? .0 : 1.0;
return IgnorePointer(
ignoring: opacity == 0,
child: AnimatedOpacity(
opacity: opacity,
duration: context.select<DurationsData, Duration>((v) => v.viewerOverlayAnimation),
child: MapOverlayButton.icon(
icon: Transform(
origin: iconSize.center(Offset.zero),
transform: Matrix4.rotationZ(degToRadian(degrees)),
child: CustomPaint(
painter: CompassPainter(
color: iconTheme.color!,
),
size: iconSize,
),
),
onPressed: widget.controller.resetRotation,
tooltip: context.l10n.mapPointNorthUpTooltip,
),
),
);
},
),
], ],
), ),
), ),
@ -161,30 +101,10 @@ class _MapButtonPanelState extends State<MapButtonPanel> {
// key is expected by test driver // key is expected by test driver
child: Column( child: Column(
children: [ children: [
if (actions.length == 1) _buildActionButton(context, actions.first), if (topRightButton != null) ...[
if (actions.length > 1) topRightButton,
MapOverlayButton(builder: (context, visualDensity, child) { SizedBox(height: padding),
final animations = context.read<Settings>().accessibilityAnimations; ],
return PopupMenuButton<MapAction>(
itemBuilder: (context) => actions
.map((action) => PopupMenuItem(
value: action,
child: MenuRow(
text: action.getText(context),
icon: action.getIcon(),
),
))
.toList(),
onSelected: (action) async {
// wait for the popup menu to hide before proceeding with the action
await Future.delayed(animations.popUpAnimationDelay * timeDilation);
_actionDelegate.onActionSelected(context, action);
},
iconSize: MapOverlayButton.iconSize(visualDensity),
popUpAnimationStyle: animations.popUpAnimationStyle,
);
}),
SizedBox(height: padding),
// key is expected by test driver // key is expected by test driver
_buildActionButton(context, MapAction.selectStyle, buttonKey: const Key('map-menu-layers')), _buildActionButton(context, MapAction.selectStyle, buttonKey: const Key('map-menu-layers')),
], ],
@ -212,10 +132,133 @@ class _MapButtonPanelState extends State<MapButtonPanel> {
); );
} }
Widget _buildActionButton(BuildContext context, MapAction action, {Key? buttonKey}) => MapOverlayButton.icon( Widget? _buildNavigationButton(BuildContext context) {
buttonKey: buttonKey, Widget? child;
icon: action.getIcon(), switch (context.select<MapThemeData, MapNavigationButton>((v) => v.navigationButton)) {
onPressed: () => _actionDelegate.onActionSelected(context, action), case MapNavigationButton.back:
tooltip: action.getText(context), if (!settings.useTvLayout) {
); child = MapOverlayButton.icon(
icon: const BackButtonIcon(),
onPressed: () => Navigator.maybeOf(context)?.pop(),
tooltip: MaterialLocalizations.of(context).backButtonTooltip,
);
}
case MapNavigationButton.close:
child = MapOverlayButton.icon(
icon: const CloseButtonIcon(),
onPressed: SystemNavigator.pop,
tooltip: MaterialLocalizations.of(context).closeButtonTooltip,
);
case MapNavigationButton.map:
final _openMapPage = widget.openMapPage;
if (_openMapPage != null) {
child = MapOverlayButton.icon(
icon: const Icon(AIcons.showFullscreenCorners),
onPressed: () => _openMapPage.call(context),
tooltip: context.l10n.openMapPageTooltip,
);
}
case MapNavigationButton.none:
break;
}
if (child != null) {
child = _heroify(context, 'top-left', child);
}
return child;
}
Widget? _buildTopRightButton(BuildContext context) {
const heroTag = 'top-right';
final actions = [
MapAction.openMapApp,
MapAction.addShortcut,
].where((action) => _actionDelegate.isVisible(context, action)).toList();
Widget? child;
if (actions.length == 1) {
child = _buildActionButton(context, actions.first, heroTag: heroTag);
} else if (actions.length > 1) {
child = MapOverlayButton(builder: (context, visualDensity, child) {
final animations = context.read<Settings>().accessibilityAnimations;
return PopupMenuButton<MapAction>(
itemBuilder: (context) => actions
.map((action) => PopupMenuItem(
value: action,
child: MenuRow(
text: action.getText(context),
icon: action.getIcon(),
),
))
.toList(),
onSelected: (action) async {
// wait for the popup menu to hide before proceeding with the action
await Future.delayed(animations.popUpAnimationDelay * timeDilation);
_actionDelegate.onActionSelected(context, action);
},
iconSize: MapOverlayButton.iconSize(visualDensity),
popUpAnimationStyle: animations.popUpAnimationStyle,
);
});
child = _heroify(context, heroTag, child);
}
return child;
}
Widget _buildCompass(BuildContext context) {
final iconTheme = IconTheme.of(context);
final iconSize = Size.square(iconTheme.size!);
return ValueListenableBuilder<ZoomedBounds>(
valueListenable: widget.boundsNotifier,
builder: (context, bounds, child) {
final degrees = bounds.rotation;
final opacity = degrees == 0 ? .0 : 1.0;
return IgnorePointer(
ignoring: opacity == 0,
child: AnimatedOpacity(
opacity: opacity,
duration: context.select<DurationsData, Duration>((v) => v.viewerOverlayAnimation),
child: MapOverlayButton.icon(
icon: Transform(
origin: iconSize.center(Offset.zero),
transform: Matrix4.rotationZ(degToRadian(degrees)),
child: CustomPaint(
painter: CompassPainter(
color: iconTheme.color!,
),
size: iconSize,
),
),
onPressed: widget.controller.resetRotation,
tooltip: context.l10n.mapPointNorthUpTooltip,
),
),
);
},
);
}
Widget _buildActionButton(BuildContext context, MapAction action, {Key? buttonKey, String? heroTag}) {
final child = MapOverlayButton.icon(
buttonKey: buttonKey,
icon: action.getIcon(),
onPressed: () => _actionDelegate.onActionSelected(context, action),
tooltip: action.getText(context),
);
return _heroify(context, heroTag ?? action.name, child);
}
Widget _heroify(BuildContext context, String? tag, Widget child) {
if (tag != null) {
final animate = context.select<Settings, bool>((v) => v.animate);
if (animate) {
return Hero(
tag: 'map-button-$tag',
flightShuttleBuilder: MapTheme.heroFlightShuttleBuilder,
child: child,
);
}
}
return child;
}
} }

View file

@ -1,10 +1,12 @@
import 'package:aves/model/settings/settings.dart';
import 'package:aves/widgets/common/fx/borders.dart'; import 'package:aves/widgets/common/fx/borders.dart';
import 'package:aves/widgets/common/providers/map_theme_provider.dart';
import 'package:aves_map/aves_map.dart'; import 'package:aves_map/aves_map.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class MapDecorator extends StatelessWidget { class MapDecorator extends StatelessWidget {
final Widget? child; final Widget child;
static const mapBorderRadius = BorderRadius.all(Radius.circular(24)); // to match button circles static const mapBorderRadius = BorderRadius.all(Radius.circular(24)); // to match button circles
static const mapBackground = Color(0xFFDBD5D3); static const mapBackground = Color(0xFFDBD5D3);
@ -12,11 +14,45 @@ class MapDecorator extends StatelessWidget {
const MapDecorator({ const MapDecorator({
super.key, super.key,
this.child, required this.child,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget _child = ClipRRect(
borderRadius: mapBorderRadius,
child: Container(
color: mapBackground,
foregroundDecoration: BoxDecoration(
border: AvesBorder.border(context),
borderRadius: mapBorderRadius,
),
child: Stack(
children: [
const GridPaper(
color: mapLoadingGrid,
interval: 10,
divisions: 1,
subdivisions: 1,
child: CustomPaint(
size: Size.infinite,
),
),
child,
],
),
),
);
final animate = context.select<Settings, bool>((v) => v.animate);
if (animate) {
_child = Hero(
tag: 'map-canvas',
flightShuttleBuilder: MapTheme.heroFlightShuttleBuilder,
child: _child,
);
}
final interactive = context.select<MapThemeData, bool>((v) => v.interactive); final interactive = context.select<MapThemeData, bool>((v) => v.interactive);
return GestureDetector( return GestureDetector(
onScaleStart: interactive onScaleStart: interactive
@ -25,30 +61,7 @@ class MapDecorator extends StatelessWidget {
// absorb scale gesture here to prevent scrolling // absorb scale gesture here to prevent scrolling
// and triggering by mistake a move to the image page above // and triggering by mistake a move to the image page above
}, },
child: ClipRRect( child: _child,
borderRadius: mapBorderRadius,
child: Container(
color: mapBackground,
foregroundDecoration: BoxDecoration(
border: AvesBorder.border(context),
borderRadius: mapBorderRadius,
),
child: Stack(
children: [
const GridPaper(
color: mapLoadingGrid,
interval: 10,
divisions: 1,
subdivisions: 1,
child: CustomPaint(
size: Size.infinite,
),
),
if (child != null) child!,
],
),
),
),
); );
} }
} }

View file

@ -20,7 +20,6 @@ import 'package:aves/widgets/common/map/attribution.dart';
import 'package:aves/widgets/common/map/buttons/panel.dart'; import 'package:aves/widgets/common/map/buttons/panel.dart';
import 'package:aves/widgets/common/map/decorator.dart'; import 'package:aves/widgets/common/map/decorator.dart';
import 'package:aves/widgets/common/map/leaflet/map.dart'; import 'package:aves/widgets/common/map/leaflet/map.dart';
import 'package:aves/widgets/common/providers/map_theme_provider.dart';
import 'package:aves/widgets/common/thumbnail/image.dart'; import 'package:aves/widgets/common/thumbnail/image.dart';
import 'package:aves/widgets/dialogs/selection_dialogs/common.dart'; import 'package:aves/widgets/dialogs/selection_dialogs/common.dart';
import 'package:aves/widgets/dialogs/selection_dialogs/single_selection.dart'; import 'package:aves/widgets/dialogs/selection_dialogs/single_selection.dart';
@ -242,50 +241,6 @@ class _GeoMapState extends State<GeoMap> {
child = _decorateMap(context, overlay); child = _decorateMap(context, overlay);
} }
child = Hero(
tag: 'map',
flightShuttleBuilder: (flightContext, animation, flightDirection, fromHeroContext, toHeroContext) {
final pushing = flightDirection == HeroFlightDirection.push;
final fromMediaQuery = MediaQuery.of(fromHeroContext);
final toMediaQuery = MediaQuery.of(toHeroContext);
final fromRenderBox = fromHeroContext.findRenderObject()! as RenderBox;
final toRenderBox = toHeroContext.findRenderObject()! as RenderBox;
final fromTheme = fromHeroContext.read<MapThemeData>();
final toTheme = toHeroContext.read<MapThemeData>();
return DefaultTextStyle(
style: DefaultTextStyle.of(toHeroContext).style,
child: AnimatedBuilder(
animation: animation,
builder: (context, child) {
final t = pushing ? animation.value : 1 - animation.value;
return MapTheme(
interactive: false,
showCoordinateFilter: false,
navigationButton: toTheme.navigationButton,
visualDensity: VisualDensity.lerp(fromTheme.visualDensity, toTheme.visualDensity, t),
child: MediaQuery(
data: toMediaQuery.copyWith(
padding: EdgeInsets.lerp(fromMediaQuery.padding, toMediaQuery.padding, t),
viewPadding: EdgeInsets.lerp(fromMediaQuery.viewPadding, toMediaQuery.viewPadding, t),
),
child: Align(
alignment: Alignment.topCenter,
child: SizedBox.fromSize(
size: Size.lerp(fromRenderBox.size, toRenderBox.size, t),
child: child,
),
),
),
);
},
child: toHeroContext.widget,
),
);
},
child: child,
);
final mapHeight = context.select<MapThemeData, double?>((v) => v.mapHeight); final mapHeight = context.select<MapThemeData, double?>((v) => v.mapHeight);
child = Column( child = Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -319,7 +274,9 @@ class _GeoMapState extends State<GeoMap> {
} }
Widget replacement = Stack( Widget replacement = Stack(
children: [ children: [
const MapDecorator(), const MapDecorator(
child: SizedBox(),
),
_buildButtonPanel(context), _buildButtonPanel(context),
], ],
); );
@ -550,7 +507,7 @@ class _GeoMapState extends State<GeoMap> {
); );
} }
Widget _decorateMap(BuildContext context, Widget? child) => MapDecorator(child: child); Widget _decorateMap(BuildContext context, Widget? child) => MapDecorator(child: child!);
Widget _buildButtonPanel(BuildContext context) { Widget _buildButtonPanel(BuildContext context) {
if (settings.useTvLayout) return const SizedBox(); if (settings.useTvLayout) return const SizedBox();

View file

@ -41,4 +41,50 @@ class MapTheme extends StatelessWidget {
child: child, child: child,
); );
} }
static Widget heroFlightShuttleBuilder(
BuildContext flightContext,
Animation<double> animation,
HeroFlightDirection flightDirection,
BuildContext fromHeroContext,
BuildContext toHeroContext,
) {
final pushing = flightDirection == HeroFlightDirection.push;
final fromMediaQuery = MediaQuery.of(fromHeroContext);
final toMediaQuery = MediaQuery.of(toHeroContext);
final fromRenderBox = fromHeroContext.findRenderObject()! as RenderBox;
final toRenderBox = toHeroContext.findRenderObject()! as RenderBox;
final fromTheme = fromHeroContext.read<MapThemeData>();
final toTheme = toHeroContext.read<MapThemeData>();
return DefaultTextStyle(
style: DefaultTextStyle.of(toHeroContext).style,
child: AnimatedBuilder(
animation: animation,
builder: (context, child) {
final t = pushing ? animation.value : 1 - animation.value;
return MapTheme(
interactive: false,
showCoordinateFilter: false,
navigationButton: toTheme.navigationButton,
visualDensity: VisualDensity.lerp(fromTheme.visualDensity, toTheme.visualDensity, t),
child: MediaQuery(
data: toMediaQuery.copyWith(
padding: EdgeInsets.lerp(fromMediaQuery.padding, toMediaQuery.padding, t),
viewPadding: EdgeInsets.lerp(fromMediaQuery.viewPadding, toMediaQuery.viewPadding, t),
),
child: Align(
alignment: Alignment.topCenter,
child: SizedBox.fromSize(
size: Size.lerp(fromRenderBox.size, toRenderBox.size, t),
child: child,
),
),
),
);
},
child: toHeroContext.widget,
),
);
}
} }

View file

@ -269,7 +269,7 @@ class _ThumbnailImageState extends State<ThumbnailImage> {
final backgroundColor = background.isColor ? background.color : null; final backgroundColor = background.isColor ? background.color : null;
image = Hero( image = Hero(
tag: heroTag, tag: heroTag,
flightShuttleBuilder: (flight, animation, direction, fromHero, toHero) { flightShuttleBuilder: (flightContext, animation, flightDirection, fromHeroContext, toHeroContext) {
Widget child = TransitionImage( Widget child = TransitionImage(
image: entry.bestCachedThumbnail, image: entry.bestCachedThumbnail,
animation: animation, animation: animation,
@ -304,11 +304,11 @@ class _ThumbnailImageState extends State<ThumbnailImage> {
if (animate && heroTag != null) { if (animate && heroTag != null) {
child = Hero( child = Hero(
tag: heroTag, tag: heroTag,
flightShuttleBuilder: (flight, animation, direction, fromHero, toHero) { flightShuttleBuilder: (flightContext, animation, flightDirection, fromHeroContext, toHeroContext) {
return MediaQueryDataProvider( return MediaQueryDataProvider(
child: DefaultTextStyle( child: DefaultTextStyle(
style: DefaultTextStyle.of(toHero).style, style: DefaultTextStyle.of(toHeroContext).style,
child: toHero.widget, child: toHeroContext.widget,
), ),
); );
}, },

View file

@ -104,11 +104,11 @@ class _AppBottomNavBarState extends State<AppBottomNavBar> {
if (animate) { if (animate) {
child = Hero( child = Hero(
tag: 'nav-bar', tag: 'nav-bar',
flightShuttleBuilder: (flight, animation, direction, fromHero, toHero) { flightShuttleBuilder: (flightContext, animation, flightDirection, fromHeroContext, toHeroContext) {
return MediaQuery.removeViewInsets( return MediaQuery.removeViewInsets(
context: context, context: context,
removeBottom: true, removeBottom: true,
child: toHero.widget, child: toHeroContext.widget,
); );
}, },
child: child, child: child,