diff --git a/lib/widgets/button.dart b/lib/widgets/button.dart index e486cccab9..4bc62fb309 100644 --- a/lib/widgets/button.dart +++ b/lib/widgets/button.dart @@ -1,3 +1,4 @@ +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'color.dart'; @@ -159,7 +160,7 @@ class ZulipWebUiKitButton extends StatelessWidget { final labelColor = _labelColor(designVariables); - return AnimatedScaleOnTap( + return AnimatedScaleOnPrimaryPointerDown( scaleEnd: 0.96, duration: Duration(milliseconds: 100), child: TextButton.icon( @@ -273,10 +274,10 @@ class ZulipIconButton extends StatelessWidget { } } -/// Apply [Transform.scale] to the child widget when tapped, and reset its scale -/// when released, while animating the transitions. -class AnimatedScaleOnTap extends StatefulWidget { - const AnimatedScaleOnTap({ +/// Apply [Transform.scale] to the child widget on primary pointer-down, +/// and reset its scale on -up or -cancel, with animated transitions. +class AnimatedScaleOnPrimaryPointerDown extends StatefulWidget { + const AnimatedScaleOnPrimaryPointerDown({ super.key, required this.scaleEnd, required this.duration, @@ -292,10 +293,10 @@ class AnimatedScaleOnTap extends StatefulWidget { final Widget child; @override - State createState() => _AnimatedScaleOnTapState(); + State createState() => _AnimatedScaleOnPrimaryPointerDownState(); } -class _AnimatedScaleOnTapState extends State { +class _AnimatedScaleOnPrimaryPointerDownState extends State { double _scale = 1; void _changeScale(double scale) { @@ -306,11 +307,13 @@ class _AnimatedScaleOnTapState extends State { @override Widget build(BuildContext context) { - return GestureDetector( + return Listener( behavior: HitTestBehavior.translucent, - onTapDown: (_) => _changeScale(widget.scaleEnd), - onTapUp: (_) => _changeScale(1), - onTapCancel: () => _changeScale(1), + onPointerDown: (PointerDownEvent pointerDownEvent) { + if((pointerDownEvent.buttons & kPrimaryButton) != 0) _changeScale(widget.scaleEnd); + }, + onPointerUp: (_) => _changeScale(1), + onPointerCancel: (_) => _changeScale(1), child: AnimatedScale( scale: _scale, duration: widget.duration, diff --git a/lib/widgets/home.dart b/lib/widgets/home.dart index 3c0a58d6ee..c74217e9b9 100644 --- a/lib/widgets/home.dart +++ b/lib/widgets/home.dart @@ -289,7 +289,7 @@ class _NavigationBarButton extends StatelessWidget { final designVariables = DesignVariables.of(context); final color = selected ? designVariables.iconSelected : designVariables.icon; - Widget result = AnimatedScaleOnTap( + Widget result = AnimatedScaleOnPrimaryPointerDown( scaleEnd: 0.875, duration: const Duration(milliseconds: 100), child: Material( @@ -410,7 +410,7 @@ class _MainMenu extends StatelessWidget { child: Column(children: menuItems)))), const Padding( padding: EdgeInsets.symmetric(horizontal: 16), - child: AnimatedScaleOnTap( + child: AnimatedScaleOnPrimaryPointerDown( scaleEnd: 0.95, duration: Duration(milliseconds: 100), child: BottomSheetDismissButton( @@ -551,7 +551,7 @@ abstract class _MenuButton extends StatelessWidget { final trailing = buildTrailing(context); - return AnimatedScaleOnTap( + return AnimatedScaleOnPrimaryPointerDown( duration: const Duration(milliseconds: 100), scaleEnd: 0.95, child: ConstrainedBox( diff --git a/test/widgets/button_test.dart b/test/widgets/button_test.dart index acbdb9cfac..87a387a359 100644 --- a/test/widgets/button_test.dart +++ b/test/widgets/button_test.dart @@ -114,6 +114,37 @@ void main() { check(renderObject).size.equals(Size.square(40)); }); + group('AnimatedScaleOnPrimaryPointerDown', () { + void checkScale(WidgetTester tester, Finder finder, double expectedScale) { + final scale = tester.widget(finder).scale; + check(scale).equals(expectedScale); + } + + testWidgets('Animation happen instantly when pointer down', (tester) async { + addTearDown(testBinding.reset); + + await tester.pumpWidget(TestZulipApp( + child: AnimatedScaleOnPrimaryPointerDown( + scaleEnd: 0.95, + duration: Duration(milliseconds: 100), + child: UnconstrainedBox( + child: ZulipIconButton( + icon: ZulipIcons.follow, + onPressed: () {}))))); + await tester.pump(); + + final animatedScaleFinder = find.byType(AnimatedScale); + + final gesture = await tester.startGesture(tester.getCenter(find.byType(ZulipIconButton))); + await tester.pump(); + checkScale(tester, animatedScaleFinder, 0.95); + + await gesture.up(); + await tester.pump(); + checkScale(tester, animatedScaleFinder, 1.0); + }); + }); + // TODO test that the touch feedback fills the whole square }); }