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 @@
// 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:flutter/widgets.dart';
import 'package:flutter/foundation.dart';
import 'colors.dart';
import 'debug.dart';
......@@ -62,7 +65,7 @@ class _AccountPictures extends StatelessWidget {
}
}
class _AccountDetails extends StatelessWidget {
class _AccountDetails extends StatefulWidget {
const _AccountDetails({
Key key,
@required this.accountName,
......@@ -76,15 +79,58 @@ class _AccountDetails extends StatelessWidget {
final VoidCallback onTap;
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
Widget build(BuildContext context) {
assert(debugCheckHasDirectionality(context));
assert(debugCheckHasMaterialLocalizations(context));
assert(debugCheckHasMaterialLocalizations(context));
final ThemeData theme = Theme.of(context);
final List<Widget> children = <Widget>[];
if (accountName != null) {
if (widget.accountName != null) {
final Widget accountNameLine = LayoutId(
id: _AccountDetailsLayout.accountName,
child: Padding(
......@@ -92,14 +138,14 @@ class _AccountDetails extends StatelessWidget {
child: DefaultTextStyle(
style: theme.primaryTextTheme.body2,
overflow: TextOverflow.ellipsis,
child: accountName,
child: widget.accountName,
),
),
);
children.add(accountNameLine);
}
if (accountEmail != null) {
if (widget.accountEmail != null) {
final Widget accountEmailLine = LayoutId(
id: _AccountDetailsLayout.accountEmail,
child: Padding(
......@@ -107,31 +153,33 @@ class _AccountDetails extends StatelessWidget {
child: DefaultTextStyle(
style: theme.primaryTextTheme.body1,
overflow: TextOverflow.ellipsis,
child: accountEmail,
child: widget.accountEmail,
),
),
);
children.add(accountEmailLine);
}
if (onTap != null) {
if (widget.onTap != null) {
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
final Widget dropDownIcon = LayoutId(
id: _AccountDetailsLayout.dropdownIcon,
child: Semantics(
container: true,
button: true,
onTap: onTap,
onTap: widget.onTap,
child: SizedBox(
height: _kAccountDetailsHeight,
width: _kAccountDetailsHeight,
child: Center(
child: Icon(
isOpen ? Icons.arrow_drop_up : Icons.arrow_drop_down,
color: Colors.white,
semanticLabel: isOpen
child: Transform.rotate(
angle: _animation.value * math.pi,
child: Icon(
Icons.arrow_drop_down,
color: Colors.white,
semanticLabel: widget.isOpen
? localizations.hideAccountsLabel
: localizations.showAccountsLabel,
),
),
),
),
......@@ -147,9 +195,9 @@ class _AccountDetails extends StatelessWidget {
children: children,
);
if (onTap != null) {
if (widget.onTap != null) {
accountDetails = InkWell(
onTap: onTap,
onTap: widget.onTap,
child: accountDetails,
excludeFromSemantics: true,
);
......
......@@ -103,6 +103,81 @@ void main() {
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 {
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