Unverified Commit 75ca39f8 authored by jslavitz's avatar jslavitz Committed by GitHub

Animate user account drawer header arrow (#24023)

* Adds user account header arrow animation and test
parent dc4bf652
...@@ -2,7 +2,10 @@ ...@@ -2,7 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:math' as math;
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter/foundation.dart';
import 'colors.dart'; import 'colors.dart';
import 'debug.dart'; import 'debug.dart';
...@@ -62,7 +65,7 @@ class _AccountPictures extends StatelessWidget { ...@@ -62,7 +65,7 @@ class _AccountPictures extends StatelessWidget {
} }
} }
class _AccountDetails extends StatelessWidget { class _AccountDetails extends StatefulWidget {
const _AccountDetails({ const _AccountDetails({
Key key, Key key,
@required this.accountName, @required this.accountName,
...@@ -76,15 +79,58 @@ class _AccountDetails extends StatelessWidget { ...@@ -76,15 +79,58 @@ class _AccountDetails extends StatelessWidget {
final VoidCallback onTap; final VoidCallback onTap;
final bool isOpen; final bool isOpen;
@override
_AccountDetailsState createState() => _AccountDetailsState();
}
class _AccountDetailsState extends State<_AccountDetails> with SingleTickerProviderStateMixin {
Animation<double> _animation;
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 (_animation.status == AnimationStatus.dismissed ||
_animation.status == AnimationStatus.reverse) {
_controller.forward();
} else {
_controller.reverse();
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(debugCheckHasDirectionality(context)); assert(debugCheckHasDirectionality(context));
assert(debugCheckHasMaterialLocalizations(context)); assert(debugCheckHasMaterialLocalizations(context));
assert(debugCheckHasMaterialLocalizations(context));
final ThemeData theme = Theme.of(context); final ThemeData theme = Theme.of(context);
final List<Widget> children = <Widget>[]; final List<Widget> children = <Widget>[];
if (accountName != null) { if (widget.accountName != null) {
final Widget accountNameLine = LayoutId( final Widget accountNameLine = LayoutId(
id: _AccountDetailsLayout.accountName, id: _AccountDetailsLayout.accountName,
child: Padding( child: Padding(
...@@ -92,14 +138,14 @@ class _AccountDetails extends StatelessWidget { ...@@ -92,14 +138,14 @@ class _AccountDetails extends StatelessWidget {
child: DefaultTextStyle( child: DefaultTextStyle(
style: theme.primaryTextTheme.body2, style: theme.primaryTextTheme.body2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
child: accountName, child: widget.accountName,
), ),
), ),
); );
children.add(accountNameLine); children.add(accountNameLine);
} }
if (accountEmail != null) { if (widget.accountEmail != null) {
final Widget accountEmailLine = LayoutId( final Widget accountEmailLine = LayoutId(
id: _AccountDetailsLayout.accountEmail, id: _AccountDetailsLayout.accountEmail,
child: Padding( child: Padding(
...@@ -107,31 +153,33 @@ class _AccountDetails extends StatelessWidget { ...@@ -107,31 +153,33 @@ class _AccountDetails extends StatelessWidget {
child: DefaultTextStyle( child: DefaultTextStyle(
style: theme.primaryTextTheme.body1, style: theme.primaryTextTheme.body1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
child: accountEmail, child: widget.accountEmail,
), ),
), ),
); );
children.add(accountEmailLine); children.add(accountEmailLine);
} }
if (widget.onTap != null) {
if (onTap != null) {
final MaterialLocalizations localizations = MaterialLocalizations.of(context); final MaterialLocalizations localizations = MaterialLocalizations.of(context);
final Widget dropDownIcon = LayoutId( final Widget dropDownIcon = LayoutId(
id: _AccountDetailsLayout.dropdownIcon, id: _AccountDetailsLayout.dropdownIcon,
child: Semantics( child: Semantics(
container: true, container: true,
button: true, button: true,
onTap: onTap, onTap: widget.onTap,
child: SizedBox( child: SizedBox(
height: _kAccountDetailsHeight, height: _kAccountDetailsHeight,
width: _kAccountDetailsHeight, width: _kAccountDetailsHeight,
child: Center( child: Center(
child: Icon( child: Transform.rotate(
isOpen ? Icons.arrow_drop_up : Icons.arrow_drop_down, angle: _animation.value * math.pi,
color: Colors.white, child: Icon(
semanticLabel: isOpen Icons.arrow_drop_down,
color: Colors.white,
semanticLabel: widget.isOpen
? localizations.hideAccountsLabel ? localizations.hideAccountsLabel
: localizations.showAccountsLabel, : localizations.showAccountsLabel,
),
), ),
), ),
), ),
...@@ -147,9 +195,9 @@ class _AccountDetails extends StatelessWidget { ...@@ -147,9 +195,9 @@ class _AccountDetails extends StatelessWidget {
children: children, children: children,
); );
if (onTap != null) { if (widget.onTap != null) {
accountDetails = InkWell( accountDetails = InkWell(
onTap: onTap, onTap: widget.onTap,
child: accountDetails, child: accountDetails,
excludeFromSemantics: true, excludeFromSemantics: true,
); );
......
...@@ -103,6 +103,81 @@ void main() { ...@@ -103,6 +103,81 @@ void main() {
expect(avatarDTopRight.dx - avatarCTopRight.dx, equals(40.0 + 16.0)); // size + space between expect(avatarDTopRight.dx - avatarCTopRight.dx, equals(40.0 + 16.0)); // size + space between
}); });
testWidgets('UserAccountsDrawerHeader icon rotation test', (WidgetTester tester) async {
await pumpTestWidget(tester);
Transform transformWidget = tester.firstWidget(find.byType(Transform));
// Icon is right side up.
expect(transformWidget.transform.getRotation()[0], 1.0);
expect(transformWidget.transform.getRotation()[4], 1.0);
await tester.tap(find.byType(Icon));
await tester.pump();
await tester.pump(Duration(milliseconds: 10));
expect(tester.hasRunningAnimations, isTrue);
await tester.pumpAndSettle();
await tester.pump();
transformWidget = tester.firstWidget(find.byType(Transform));
// Icon has rotated 180 degrees.
expect(transformWidget.transform.getRotation()[0], -1.0);
expect(transformWidget.transform.getRotation()[4], -1.0);
await tester.tap(find.byType(Icon));
await tester.pump();
await tester.pump(Duration(milliseconds: 10));
expect(tester.hasRunningAnimations, isTrue);
await tester.pumpAndSettle();
await tester.pump();
transformWidget = tester.firstWidget(find.byType(Transform));
// Icon has rotated 180 degrees back to the original position.
expect(transformWidget.transform.getRotation()[0], 1.0);
expect(transformWidget.transform.getRotation()[4], 1.0);
});
testWidgets('UserAccountsDrawerHeader icon rotation test speeeeeedy', (WidgetTester tester) async {
await pumpTestWidget(tester);
Transform transformWidget = tester.firstWidget(find.byType(Transform));
// Icon is right side up.
expect(transformWidget.transform.getRotation()[0], 1.0);
expect(transformWidget.transform.getRotation()[4], 1.0);
// Icon starts to rotate down.
await tester.tap(find.byType(Icon));
await tester.pump();
await tester.pump(Duration(milliseconds: 100));
expect(tester.hasRunningAnimations, isTrue);
// Icon starts to rotate up mid animation.
await tester.tap(find.byType(Icon));
await tester.pump();
await tester.pump(Duration(milliseconds: 100));
expect(tester.hasRunningAnimations, isTrue);
// Icon starts to rotate down again still mid animation.
await tester.tap(find.byType(Icon));
await tester.pump();
await tester.pump(Duration(milliseconds: 100));
expect(tester.hasRunningAnimations, isTrue);
// Icon starts to rotate up to its original position mid animation.
await tester.tap(find.byType(Icon));
await tester.pump();
await tester.pump(Duration(milliseconds: 100));
expect(tester.hasRunningAnimations, isTrue);
await tester.pumpAndSettle();
await tester.pump();
transformWidget = tester.firstWidget(find.byType(Transform));
// Icon has rotated 180 degrees back to the original position.
expect(transformWidget.transform.getRotation()[0], 1.0);
expect(transformWidget.transform.getRotation()[4], 1.0);
});
testWidgets('UserAccountsDrawerHeader null parameters LTR', (WidgetTester tester) async { testWidgets('UserAccountsDrawerHeader null parameters LTR', (WidgetTester tester) async {
Widget buildFrame({ Widget buildFrame({
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment