[Gallery] Add animation for settings backdrop (#252)

* Lift up AnimationController to parnet widget AnimatedBackdrop so it can be shared with child widgets. Make the boolean 'isSettingsOpen' a ValueNotifier so we can listen to changes for it in SettingsPage.

* When closing settings, also shrink expanded setting.

* Animate the settings page to slide in from above.

* Add in stagger animation for setting items.

* Make sure that state is updated so the test passes

* Use setState for when closing expanded setting

* Move build method last, move animations initialization to initState and fix spelling mistake
pull/257/head
Per Classon 5 years ago committed by GitHub
parent be484af4d2
commit 264d697c61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -12,8 +12,6 @@ import 'package:gallery/constants.dart';
import 'package:gallery/data/gallery_options.dart';
import 'package:gallery/l10n/gallery_localizations.dart';
import 'package:gallery/pages/backdrop.dart';
import 'package:gallery/pages/home.dart';
import 'package:gallery/pages/settings.dart';
import 'package:gallery/pages/splash.dart';
import 'package:gallery/themes/gallery_theme_data.dart';
@ -70,10 +68,7 @@ class GalleryApp extends StatelessWidget {
},
home: ApplyTextOptions(
child: SplashPage(
child: Backdrop(
frontLayer: SettingsPage(),
backLayer: HomePage(),
),
child: AnimatedBackdrop(),
),
),
);

@ -13,17 +13,93 @@ import 'package:gallery/constants.dart';
import 'package:gallery/data/gallery_options.dart';
import 'package:gallery/l10n/gallery_localizations.dart';
import 'package:gallery/layout/adaptive.dart';
import 'package:gallery/pages/home.dart';
import 'package:gallery/pages/settings.dart';
class AnimatedBackdrop extends StatefulWidget {
@override
_AnimatedBackdropState createState() => _AnimatedBackdropState();
}
class _AnimatedBackdropState extends State<AnimatedBackdrop>
with SingleTickerProviderStateMixin {
AnimationController backdropController;
ValueNotifier<bool> isSettingsOpenNotifier;
Animation<double> openSettingsAnimation;
Animation<double> staggerSettingsItemsAnimation;
@override
void initState() {
super.initState();
backdropController = AnimationController(
duration: Duration(milliseconds: 200),
vsync: this,
)..addListener(() {
setState(() {
// The state that has changed here is the animation.
});
});
isSettingsOpenNotifier = ValueNotifier(false);
openSettingsAnimation = CurvedAnimation(
parent: backdropController,
curve: Interval(
0.0,
0.4,
curve: Curves.ease,
),
);
staggerSettingsItemsAnimation = CurvedAnimation(
parent: backdropController,
curve: Interval(
0.5,
1.0,
curve: Curves.easeIn,
),
);
}
@override
void dispose() {
backdropController.dispose();
isSettingsOpenNotifier.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Backdrop(
controller: backdropController,
isSettingsOpenNotifier: isSettingsOpenNotifier,
openSettingsAnimation: openSettingsAnimation,
frontLayer: SettingsPage(
openSettingsAnimation: openSettingsAnimation,
staggerSettingsItemsAnimation: staggerSettingsItemsAnimation,
isSettingsOpenNotifier: isSettingsOpenNotifier,
),
backLayer: HomePage(),
);
}
}
class Backdrop extends StatefulWidget {
final Widget frontLayer;
final Widget backLayer;
final AnimationController controller;
final Animation<double> openSettingsAnimation;
final ValueNotifier<bool> isSettingsOpenNotifier;
Backdrop({
Key key,
@required this.frontLayer,
@required this.backLayer,
@required this.controller,
@required this.openSettingsAnimation,
@required this.isSettingsOpenNotifier,
}) : assert(frontLayer != null),
assert(backLayer != null),
assert(controller != null),
assert(isSettingsOpenNotifier != null),
assert(openSettingsAnimation != null),
super(key: key);
@override
@ -32,8 +108,6 @@ class Backdrop extends StatefulWidget {
class _BackdropState extends State<Backdrop>
with SingleTickerProviderStateMixin, FlareController {
AnimationController _controller;
Animation<double> _animationReversed;
FlareAnimationLayer _animationLayer;
FlutterActorArtboard _artboard;
@ -41,7 +115,6 @@ class _BackdropState extends State<Backdrop>
double settingsButtonHeightDesktop = 56;
double settingsButtonHeightMobile = 40;
bool _isSettingsOpen;
FocusNode frontLayerFocusNode;
FocusNode backLayerFocusNode;
@ -50,20 +123,10 @@ class _BackdropState extends State<Backdrop>
super.initState();
frontLayerFocusNode = FocusNode();
backLayerFocusNode = FocusNode();
_isSettingsOpen = false;
_controller = AnimationController(
duration: Duration(milliseconds: 100), value: 1, vsync: this)
..addListener(() {
this.setState(() {});
});
_animationReversed =
Tween<double>(begin: 1.0, end: 0.0).animate(_controller);
}
@override
void dispose() {
_controller.dispose();
frontLayerFocusNode.dispose();
backLayerFocusNode.dispose();
super.dispose();
@ -84,7 +147,7 @@ class _BackdropState extends State<Backdrop>
bool advance(FlutterActorArtboard artboard, double elapsed) {
if (_animationLayer != null) {
FlareAnimationLayer layer = _animationLayer;
layer.time = _animationReversed.value * layer.duration;
layer.time = widget.controller.value * layer.duration;
layer.animation.apply(layer.time, _artboard, 1);
if (layer.isDone || layer.time == 0) {
_animationLayer = null;
@ -104,10 +167,16 @@ class _BackdropState extends State<Backdrop>
}
void toggleSettings() {
_controller.fling(velocity: _isSettingsOpen ? 1 : -1);
// Animate the settings panel to open or close.
widget.controller
.fling(velocity: widget.isSettingsOpenNotifier.value ? -1 : 1);
setState(() {
widget.isSettingsOpenNotifier.value =
!widget.isSettingsOpenNotifier.value;
});
// Animate the settings icon.
initAnimationLayer();
isActive.value = true;
_isSettingsOpen = !_isSettingsOpen;
}
Animation<RelativeRect> _getPanelAnimation(BoxConstraints constraints) {
@ -115,14 +184,17 @@ class _BackdropState extends State<Backdrop>
final double top = height - galleryHeaderHeight;
final double bottom = -galleryHeaderHeight;
return RelativeRectTween(
begin: RelativeRect.fromLTRB(0, top, 0, bottom),
end: RelativeRect.fromLTRB(0, 0, 0, 0),
).animate(CurvedAnimation(parent: _controller, curve: Curves.linear));
begin: RelativeRect.fromLTRB(0, 0, 0, 0),
end: RelativeRect.fromLTRB(0, top, 0, bottom),
).animate(CurvedAnimation(
parent: widget.openSettingsAnimation,
curve: Curves.linear,
));
}
Widget _galleryHeader() {
return ExcludeSemantics(
excluding: _isSettingsOpen,
excluding: widget.isSettingsOpenNotifier.value,
child: Semantics(
sortKey: OrdinalSortKey(
GalleryOptions.of(context).textDirection() == TextDirection.ltr
@ -137,7 +209,6 @@ class _BackdropState extends State<Backdrop>
}
Widget _buildStack(BuildContext context, BoxConstraints constraints) {
final Animation<RelativeRect> animation = _getPanelAnimation(constraints);
final isDesktop = isDisplayDesktop(context);
final safeAreaTopPadding = MediaQuery.of(context).padding.top;
@ -145,21 +216,15 @@ class _BackdropState extends State<Backdrop>
child: DefaultFocusTraversal(
policy: WidgetOrderFocusTraversalPolicy(),
child: Focus(
skipTraversal: !_isSettingsOpen,
child: InheritedBackdrop(
toggleSettings: toggleSettings,
child: widget.frontLayer,
settingsButtonWidth: settingsButtonWidth,
desktopSettingsButtonHeight: settingsButtonHeightDesktop,
mobileSettingsButtonHeight: settingsButtonHeightMobile,
),
skipTraversal: !widget.isSettingsOpenNotifier.value,
child: widget.frontLayer,
),
),
excluding: !_isSettingsOpen,
excluding: !widget.isSettingsOpenNotifier.value,
);
final Widget backLayer = ExcludeSemantics(
child: widget.backLayer,
excluding: _isSettingsOpen,
excluding: widget.isSettingsOpenNotifier.value,
);
return DefaultFocusTraversal(
@ -173,14 +238,14 @@ class _BackdropState extends State<Backdrop>
_galleryHeader(),
frontLayer,
PositionedTransition(
rect: animation,
rect: _getPanelAnimation(constraints),
child: backLayer,
),
],
if (isDesktop) ...[
_galleryHeader(),
backLayer,
if (_isSettingsOpen) ...[
if (widget.isSettingsOpenNotifier.value) ...[
ExcludeSemantics(
child: ModalBarrier(
dismissible: true,
@ -199,7 +264,9 @@ class _BackdropState extends State<Backdrop>
? Alignment.topRight
: Alignment.topLeft,
scale: CurvedAnimation(
parent: _animationReversed,
parent: isDesktop
? widget.controller
: widget.openSettingsAnimation,
curve: Curves.easeIn,
reverseCurve: Curves.easeOut,
),
@ -226,7 +293,7 @@ class _BackdropState extends State<Backdrop>
alignment: AlignmentDirectional.topEnd,
child: Semantics(
button: true,
label: _isSettingsOpen
label: widget.isSettingsOpenNotifier.value
? GalleryLocalizations.of(context)
.settingsButtonCloseLabel
: GalleryLocalizations.of(context).settingsButtonLabel,
@ -239,7 +306,8 @@ class _BackdropState extends State<Backdrop>
borderRadius: BorderRadiusDirectional.only(
bottomStart: Radius.circular(10),
),
color: _isSettingsOpen & !_controller.isAnimating
color: widget.isSettingsOpenNotifier.value &
!widget.controller.isAnimating
? Colors.transparent
: Theme.of(context).colorScheme.secondaryVariant,
clipBehavior: Clip.antiAlias,
@ -252,7 +320,7 @@ class _BackdropState extends State<Backdrop>
onFocusChange: (hasFocus) {
if (!hasFocus) {
FocusScope.of(context).requestFocus(
(_isSettingsOpen)
(widget.isSettingsOpenNotifier.value)
? frontLayerFocusNode
: backLayerFocusNode);
}
@ -291,30 +359,6 @@ class _BackdropState extends State<Backdrop>
}
}
class InheritedBackdrop extends InheritedWidget {
final void Function() toggleSettings;
final double settingsButtonWidth;
final double desktopSettingsButtonHeight;
final double mobileSettingsButtonHeight;
InheritedBackdrop({
this.toggleSettings,
this.settingsButtonWidth,
this.desktopSettingsButtonHeight,
this.mobileSettingsButtonHeight,
Widget child,
}) : super(child: child);
@override
bool updateShouldNotify(InheritedWidget oldWidget) {
return true;
}
static InheritedBackdrop of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType();
}
}
class InheritedBackdropFocusNodes extends InheritedWidget {
InheritedBackdropFocusNodes({
@required Widget child,

@ -4,7 +4,7 @@
import 'dart:collection';
import "package:collection/collection.dart";
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localized_countries/flutter_localized_countries.dart';
import 'package:gallery/constants.dart';
@ -26,6 +26,17 @@ enum _ExpandableSetting {
}
class SettingsPage extends StatefulWidget {
SettingsPage({
Key key,
@required this.openSettingsAnimation,
@required this.staggerSettingsItemsAnimation,
@required this.isSettingsOpenNotifier,
}) : super(key: key);
final Animation<double> openSettingsAnimation;
final Animation<double> staggerSettingsItemsAnimation;
final ValueNotifier<bool> isSettingsOpenNotifier;
@override
_SettingsPageState createState() => _SettingsPageState();
}
@ -54,6 +65,15 @@ class _SettingsPageState extends State<SettingsPage> {
},
),
);
// When closing settings, also shrink expanded setting.
widget.isSettingsOpenNotifier.addListener(() {
if (!widget.isSettingsOpenNotifier.value) {
setState(() {
expandedSettingId = null;
});
}
});
}
/// Given a [Locale], returns a [DisplayOption] with its native name for a
@ -119,156 +139,168 @@ class _SettingsPageState extends State<SettingsPage> {
final options = GalleryOptions.of(context);
final isDesktop = isDisplayDesktop(context);
final settingsListItems = [
SettingsListItem<double>(
title: GalleryLocalizations.of(context).settingsTextScaling,
selectedOption: options.textScaleFactor(
context,
useSentinel: true,
),
options: LinkedHashMap.of({
systemTextScaleFactorOption: DisplayOption(
GalleryLocalizations.of(context).settingsSystemDefault,
),
0.8: DisplayOption(
GalleryLocalizations.of(context).settingsTextScalingSmall,
),
1.0: DisplayOption(
GalleryLocalizations.of(context).settingsTextScalingNormal,
),
2.0: DisplayOption(
GalleryLocalizations.of(context).settingsTextScalingLarge,
),
3.0: DisplayOption(
GalleryLocalizations.of(context).settingsTextScalingHuge,
),
}),
onOptionChanged: (newTextScale) => GalleryOptions.update(
context,
options.copyWith(textScaleFactor: newTextScale),
),
onTapSetting: () => onTapSetting(_ExpandableSetting.textScale),
isExpanded: expandedSettingId == _ExpandableSetting.textScale,
),
SettingsListItem<CustomTextDirection>(
title: GalleryLocalizations.of(context).settingsTextDirection,
selectedOption: options.customTextDirection,
options: LinkedHashMap.of({
CustomTextDirection.localeBased: DisplayOption(
GalleryLocalizations.of(context).settingsTextDirectionLocaleBased,
),
CustomTextDirection.ltr: DisplayOption(
GalleryLocalizations.of(context).settingsTextDirectionLTR,
),
CustomTextDirection.rtl: DisplayOption(
GalleryLocalizations.of(context).settingsTextDirectionRTL,
),
}),
onOptionChanged: (newTextDirection) => GalleryOptions.update(
context,
options.copyWith(customTextDirection: newTextDirection),
),
onTapSetting: () => onTapSetting(_ExpandableSetting.textDirection),
isExpanded: expandedSettingId == _ExpandableSetting.textDirection,
),
SettingsListItem<Locale>(
title: GalleryLocalizations.of(context).settingsLocale,
selectedOption: options.locale == deviceLocale
? systemLocaleOption
: options.locale,
options: _getLocaleOptions(),
onOptionChanged: (newLocale) {
if (newLocale == systemLocaleOption) {
newLocale = deviceLocale;
}
GalleryOptions.update(
context,
options.copyWith(locale: newLocale),
);
},
onTapSetting: () => onTapSetting(_ExpandableSetting.locale),
isExpanded: expandedSettingId == _ExpandableSetting.locale,
),
SettingsListItem<TargetPlatform>(
title: GalleryLocalizations.of(context).settingsPlatformMechanics,
selectedOption: options.platform,
options: LinkedHashMap.of({
TargetPlatform.android: DisplayOption(
GalleryLocalizations.of(context).settingsPlatformAndroid,
),
TargetPlatform.iOS: DisplayOption(
GalleryLocalizations.of(context).settingsPlatformIOS,
),
}),
onOptionChanged: (newPlatform) => GalleryOptions.update(
context,
options.copyWith(platform: newPlatform),
),
onTapSetting: () => onTapSetting(_ExpandableSetting.platform),
isExpanded: expandedSettingId == _ExpandableSetting.platform,
),
SettingsListItem<ThemeMode>(
title: GalleryLocalizations.of(context).settingsTheme,
selectedOption: options.themeMode,
options: LinkedHashMap.of({
ThemeMode.system: DisplayOption(
GalleryLocalizations.of(context).settingsSystemDefault,
),
ThemeMode.dark: DisplayOption(
GalleryLocalizations.of(context).settingsDarkTheme,
),
ThemeMode.light: DisplayOption(
GalleryLocalizations.of(context).settingsLightTheme,
),
}),
onOptionChanged: (newThemeMode) => GalleryOptions.update(
context,
options.copyWith(themeMode: newThemeMode),
),
onTapSetting: () => onTapSetting(_ExpandableSetting.theme),
isExpanded: expandedSettingId == _ExpandableSetting.theme,
),
SlowMotionSetting(),
];
return Material(
color: colorScheme.secondaryVariant,
child: Padding(
padding: isDesktop
? EdgeInsets.zero
: EdgeInsets.only(bottom: galleryHeaderHeight),
// Remove ListView top padding as it is already accounted for.
child: MediaQuery.removePadding(
removeTop: isDesktop,
context: context,
child: ListView(
children: [
SizedBox(height: firstHeaderDesktopTopPadding),
Focus(
focusNode:
InheritedBackdropFocusNodes.of(context).frontLayerFocusNode,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 32),
child: ExcludeSemantics(
child: Header(
color: Theme.of(context).colorScheme.onSurface,
text: GalleryLocalizations.of(context).settingsTitle,
),
),
child: _AnimatedSettingsPage(
animation: widget.openSettingsAnimation,
child: Padding(
padding: isDesktop
? EdgeInsets.zero
: EdgeInsets.only(
bottom: galleryHeaderHeight,
),
),
SettingsListItem<double>(
title: GalleryLocalizations.of(context).settingsTextScaling,
selectedOption: options.textScaleFactor(
context,
useSentinel: true,
),
options: LinkedHashMap.of({
systemTextScaleFactorOption: DisplayOption(
GalleryLocalizations.of(context).settingsSystemDefault,
),
0.8: DisplayOption(
GalleryLocalizations.of(context).settingsTextScalingSmall,
),
1.0: DisplayOption(
GalleryLocalizations.of(context).settingsTextScalingNormal,
),
2.0: DisplayOption(
GalleryLocalizations.of(context).settingsTextScalingLarge,
),
3.0: DisplayOption(
GalleryLocalizations.of(context).settingsTextScalingHuge,
),
}),
onOptionChanged: (newTextScale) => GalleryOptions.update(
context,
options.copyWith(textScaleFactor: newTextScale),
),
onTapSetting: () => onTapSetting(_ExpandableSetting.textScale),
isExpanded: expandedSettingId == _ExpandableSetting.textScale,
),
SettingsListItem<CustomTextDirection>(
title: GalleryLocalizations.of(context).settingsTextDirection,
selectedOption: options.customTextDirection,
options: LinkedHashMap.of({
CustomTextDirection.localeBased: DisplayOption(
GalleryLocalizations.of(context)
.settingsTextDirectionLocaleBased,
),
CustomTextDirection.ltr: DisplayOption(
GalleryLocalizations.of(context).settingsTextDirectionLTR,
),
CustomTextDirection.rtl: DisplayOption(
GalleryLocalizations.of(context).settingsTextDirectionRTL,
// Remove ListView top padding as it is already accounted for.
child: MediaQuery.removePadding(
removeTop: isDesktop,
context: context,
child: ListView(
children: [
if (isDesktop) SizedBox(height: firstHeaderDesktopTopPadding),
Focus(
focusNode: InheritedBackdropFocusNodes.of(context)
.frontLayerFocusNode,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 32),
child: ExcludeSemantics(
child: Header(
color: Theme.of(context).colorScheme.onSurface,
text: GalleryLocalizations.of(context).settingsTitle,
),
),
),
}),
onOptionChanged: (newTextDirection) => GalleryOptions.update(
context,
options.copyWith(customTextDirection: newTextDirection),
),
onTapSetting: () =>
onTapSetting(_ExpandableSetting.textDirection),
isExpanded:
expandedSettingId == _ExpandableSetting.textDirection,
),
SettingsListItem<Locale>(
title: GalleryLocalizations.of(context).settingsLocale,
selectedOption: options.locale == deviceLocale
? systemLocaleOption
: options.locale,
options: _getLocaleOptions(),
onOptionChanged: (newLocale) {
if (newLocale == systemLocaleOption) {
newLocale = deviceLocale;
}
GalleryOptions.update(
context,
options.copyWith(locale: newLocale),
);
},
onTapSetting: () => onTapSetting(_ExpandableSetting.locale),
isExpanded: expandedSettingId == _ExpandableSetting.locale,
),
SettingsListItem<TargetPlatform>(
title:
GalleryLocalizations.of(context).settingsPlatformMechanics,
selectedOption: options.platform,
options: LinkedHashMap.of({
TargetPlatform.android: DisplayOption(
GalleryLocalizations.of(context).settingsPlatformAndroid,
if (isDesktop)
...settingsListItems
else ...[
_AnimateSettingsListItems(
animation: widget.staggerSettingsItemsAnimation,
children: settingsListItems,
),
TargetPlatform.iOS: DisplayOption(
GalleryLocalizations.of(context).settingsPlatformIOS,
),
}),
onOptionChanged: (newPlatform) => GalleryOptions.update(
context,
options.copyWith(platform: newPlatform),
),
onTapSetting: () => onTapSetting(_ExpandableSetting.platform),
isExpanded: expandedSettingId == _ExpandableSetting.platform,
),
SettingsListItem<ThemeMode>(
title: GalleryLocalizations.of(context).settingsTheme,
selectedOption: options.themeMode,
options: LinkedHashMap.of({
ThemeMode.system: DisplayOption(
GalleryLocalizations.of(context).settingsSystemDefault,
),
ThemeMode.dark: DisplayOption(
GalleryLocalizations.of(context).settingsDarkTheme,
),
ThemeMode.light: DisplayOption(
GalleryLocalizations.of(context).settingsLightTheme,
),
}),
onOptionChanged: (newThemeMode) => GalleryOptions.update(
context,
options.copyWith(themeMode: newThemeMode),
),
onTapSetting: () => onTapSetting(_ExpandableSetting.theme),
isExpanded: expandedSettingId == _ExpandableSetting.theme,
),
SlowMotionSetting(),
if (!isDesktop) ...[
SizedBox(height: 16),
Divider(thickness: 2, height: 0, color: colorScheme.background),
SizedBox(height: 12),
SettingsAbout(),
SettingsFeedback(),
SizedBox(height: 12),
Divider(thickness: 2, height: 0, color: colorScheme.background),
SettingsAttribution(),
SizedBox(height: 16),
Divider(
thickness: 2, height: 0, color: colorScheme.background),
SizedBox(height: 12),
SettingsAbout(),
SettingsFeedback(),
SizedBox(height: 12),
Divider(
thickness: 2, height: 0, color: colorScheme.background),
SettingsAttribution(),
],
],
],
),
),
),
),
@ -383,3 +415,93 @@ class _SettingsLink extends StatelessWidget {
);
}
}
/// Animate the settings page to slide in from above.
class _AnimatedSettingsPage extends StatelessWidget {
const _AnimatedSettingsPage({
Key key,
@required this.animation,
@required this.child,
}) : super(key: key);
final Widget child;
final Animation<double> animation;
@override
Widget build(BuildContext context) {
final isDesktop = isDisplayDesktop(context);
if (isDesktop) {
return child;
} else {
return LayoutBuilder(builder: (context, constraints) {
return Stack(
children: [
PositionedTransition(
rect: RelativeRectTween(
begin: RelativeRect.fromLTRB(0, -constraints.maxHeight, 0, 0),
end: RelativeRect.fromLTRB(0, 0, 0, 0),
).animate(
CurvedAnimation(
parent: animation,
curve: Curves.linear,
),
),
child: child,
),
],
);
});
}
}
}
/// Animate the settings list items to stagger in from above.
class _AnimateSettingsListItems extends StatelessWidget {
const _AnimateSettingsListItems({
Key key,
this.animation,
this.children,
this.topPadding,
this.bottomPadding,
}) : super(key: key);
final Animation<double> animation;
final List<Widget> children;
final double topPadding;
final double bottomPadding;
@override
Widget build(BuildContext context) {
final startDividingPadding = 4.0;
final topPaddingTween = Tween<double>(
begin: 0,
end: children.length * startDividingPadding,
);
final dividerTween = Tween<double>(
begin: startDividingPadding,
end: 0,
);
return Padding(
padding: EdgeInsets.only(top: topPaddingTween.animate(animation).value),
child: Column(
children: [
for (Widget child in children)
AnimatedBuilder(
animation: animation,
builder: (context, child) {
return Padding(
padding: EdgeInsets.only(
top: dividerTween.animate(animation).value,
),
child: child,
);
},
child: child,
),
],
),
);
}
}

@ -10,6 +10,11 @@ import 'package:gallery/pages/backdrop.dart';
void main() {
testWidgets('Home page hides settings semantics when closed', (tester) async {
final animationController = AnimationController(
duration: Duration(milliseconds: 1),
vsync: const TestVSync(),
);
final isSettingsOpen = ValueNotifier(false);
await tester.pumpWidget(
MaterialApp(
localizationsDelegates: [GalleryLocalizations.delegate],
@ -20,6 +25,9 @@ void main() {
child: Backdrop(
frontLayer: Text('Front'),
backLayer: Text('Back'),
controller: animationController,
isSettingsOpenNotifier: isSettingsOpen,
openSettingsAnimation: animationController,
),
),
),
@ -40,5 +48,7 @@ void main() {
// bottom by utilizing an invisible widget within the Settings Page.
expect(tester.getSemantics(find.text('Back')).owner, null);
expect(tester.getSemantics(find.text('Front')).label, 'Front');
animationController.dispose();
});
}

Loading…
Cancel
Save