map: hero fixes
This commit is contained in:
parent
9aeb0a1fc3
commit
d96f9768f9
8 changed files with 275 additions and 191 deletions
|
@ -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,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -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,9 +35,7 @@ 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);
|
||||||
return Padding(
|
Widget child = MarkdownBody(
|
||||||
padding: const EdgeInsets.only(top: 4),
|
|
||||||
child: MarkdownBody(
|
|
||||||
data: '${context.l10n.mapAttributionOsmData}${AText.separator}$data',
|
data: '${context.l10n.mapAttributionOsmData}${AText.separator}$data',
|
||||||
selectable: true,
|
selectable: true,
|
||||||
styleSheet: MarkdownStyleSheet(
|
styleSheet: MarkdownStyleSheet(
|
||||||
|
@ -43,7 +43,32 @@ class Attribution extends StatelessWidget {
|
||||||
p: theme.textTheme.bodySmall!.merge(const TextStyle(fontSize: InfoRowGroup.fontSize)),
|
p: theme.textTheme.bodySmall!.merge(const TextStyle(fontSize: InfoRowGroup.fontSize)),
|
||||||
),
|
),
|
||||||
onTapLink: (text, href, title) => AvesApp.launchUrl(href),
|
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(
|
||||||
|
padding: const EdgeInsets.only(top: 4),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
|
||||||
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),
|
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) {
|
||||||
|
Widget? child;
|
||||||
|
switch (context.select<MapThemeData, MapNavigationButton>((v) => v.navigationButton)) {
|
||||||
|
case MapNavigationButton.back:
|
||||||
|
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,
|
buttonKey: buttonKey,
|
||||||
icon: action.getIcon(),
|
icon: action.getIcon(),
|
||||||
onPressed: () => _actionDelegate.onActionSelected(context, action),
|
onPressed: () => _actionDelegate.onActionSelected(context, action),
|
||||||
tooltip: action.getText(context),
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,20 +14,12 @@ 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) {
|
||||||
final interactive = context.select<MapThemeData, bool>((v) => v.interactive);
|
Widget _child = ClipRRect(
|
||||||
return GestureDetector(
|
|
||||||
onScaleStart: interactive
|
|
||||||
? null
|
|
||||||
: (details) {
|
|
||||||
// absorb scale gesture here to prevent scrolling
|
|
||||||
// and triggering by mistake a move to the image page above
|
|
||||||
},
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: mapBorderRadius,
|
borderRadius: mapBorderRadius,
|
||||||
child: Container(
|
child: Container(
|
||||||
color: mapBackground,
|
color: mapBackground,
|
||||||
|
@ -44,11 +38,30 @@ class MapDecorator extends StatelessWidget {
|
||||||
size: Size.infinite,
|
size: Size.infinite,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (child != null) child!,
|
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);
|
||||||
|
return GestureDetector(
|
||||||
|
onScaleStart: interactive
|
||||||
|
? null
|
||||||
|
: (details) {
|
||||||
|
// absorb scale gesture here to prevent scrolling
|
||||||
|
// and triggering by mistake a move to the image page above
|
||||||
|
},
|
||||||
|
child: _child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in a new issue