1+ import 'dart:ui' ;
2+
13import 'package:collection/collection.dart' ;
24import 'package:flutter/material.dart' ;
3- import 'package:flutter/semantics.dart' ;
45
56import '../api/exception.dart' ;
67import '../api/model/model.dart' ;
@@ -797,6 +798,19 @@ class ViewReactionsHeader extends StatelessWidget {
797798 final String ? emojiCode;
798799 final void Function (ReactionWithVotes ) onRequestSelect;
799800
801+ /// A [double] between 0.0 and 1.0 for an emoji's position in the list.
802+ ///
803+ /// When auto-scrolling an emoji into view,
804+ /// this is where the scroll position will land
805+ /// (the min- and max- scroll extent lerped at this value).
806+ double _emojiItemPosition (int index, int aggregatedLength) {
807+ if (aggregatedLength == 1 ) {
808+ assert (index == 0 );
809+ return 0.5 ;
810+ }
811+ return index / (aggregatedLength - 1 );
812+ }
813+
800814 @override
801815 Widget build (BuildContext context) {
802816 final designVariables = DesignVariables .of (context);
@@ -829,9 +843,10 @@ class ViewReactionsHeader extends StatelessWidget {
829843 explicitChildNodes: true ,
830844 label: zulipLocalizations.seeWhoReactedSheetHeaderLabel (reactions.total),
831845 child: Row (
832- children: reactions.aggregated.map (( r) =>
846+ children: reactions.aggregated.mapIndexed ((i, r) =>
833847 _ViewReactionsEmojiItem (
834848 reactionWithVotes: r,
849+ position: _emojiItemPosition (i, reactions.aggregated.length),
835850 selected: r.reactionType == reactionType && r.emojiCode == emojiCode,
836851 onRequestSelect: onRequestSelect),
837852 ).toList ()))))));
@@ -841,19 +856,35 @@ class ViewReactionsHeader extends StatelessWidget {
841856class _ViewReactionsEmojiItem extends StatelessWidget {
842857 const _ViewReactionsEmojiItem ({
843858 required this .reactionWithVotes,
859+ required this .position,
844860 required this .selected,
845861 required this .onRequestSelect,
846862 });
847863
848864 final ReactionWithVotes reactionWithVotes;
865+ final double position;
849866 final bool selected;
850867 final void Function (ReactionWithVotes ) onRequestSelect;
851868
852869 static const double emojiSize = 24 ;
853870
871+ /// Animates the list's scroll position for this item.
872+ ///
873+ /// This serves two purposes when the list is longer than the viewport width:
874+ /// - Ensures the item is in view
875+ /// - By animating, draws attention to the fact that this is a scrollable list
876+ /// and there may be more items in view. (In particular, does this when
877+ /// any item is tapped, because each item has a different [position] .)
854878 void _scrollIntoView (BuildContext context) {
855- Scrollable .ensureVisible (context,
856- alignment: 0.5 , duration: Duration (milliseconds: 200 ));
879+ final scrollPosition = Scrollable .of (context, axis: Axis .horizontal).position;
880+ final destination = lerpDouble (
881+ scrollPosition.minScrollExtent,
882+ scrollPosition.maxScrollExtent,
883+ position)! ;
884+
885+ scrollPosition.animateTo (destination,
886+ duration: Duration (milliseconds: 200 ),
887+ curve: Curves .ease);
857888 }
858889
859890 void _handleTap (BuildContext context) {
0 commit comments