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' ;
@@ -792,6 +793,19 @@ class ViewReactionsHeader extends StatelessWidget {
792793 final String ? emojiCode;
793794 final void Function (ReactionWithVotes ) onRequestSelect;
794795
796+ /// A [double] between 0.0 and 1.0 for an emoji's position in the list.
797+ ///
798+ /// When auto-scrolling an emoji into view,
799+ /// this is where the scroll position will land
800+ /// (the min- and max- scroll extent lerped to this value).
801+ double _emojiItemPosition (int index, int aggregatedLength) {
802+ if (aggregatedLength == 1 ) {
803+ assert (index == 0 );
804+ return 0.5 ;
805+ }
806+ return index / (aggregatedLength - 1 );
807+ }
808+
795809 @override
796810 Widget build (BuildContext context) {
797811 final designVariables = DesignVariables .of (context);
@@ -824,9 +838,10 @@ class ViewReactionsHeader extends StatelessWidget {
824838 explicitChildNodes: true ,
825839 label: zulipLocalizations.seeWhoReactedSheetHeaderLabel (reactions.total),
826840 child: Row (
827- children: reactions.aggregated.map (( r) =>
841+ children: reactions.aggregated.mapIndexed ((i, r) =>
828842 _ViewReactionsEmojiItem (
829843 reactionWithVotes: r,
844+ position: _emojiItemPosition (i, reactions.aggregated.length),
830845 selected: r.reactionType == reactionType && r.emojiCode == emojiCode,
831846 onRequestSelect: onRequestSelect),
832847 ).toList ()))))));
@@ -836,19 +851,36 @@ class ViewReactionsHeader extends StatelessWidget {
836851class _ViewReactionsEmojiItem extends StatelessWidget {
837852 const _ViewReactionsEmojiItem ({
838853 required this .reactionWithVotes,
854+ required this .position,
839855 required this .selected,
840856 required this .onRequestSelect,
841857 });
842858
843859 final ReactionWithVotes reactionWithVotes;
860+ final double position;
844861 final bool selected;
845862 final void Function (ReactionWithVotes ) onRequestSelect;
846863
847864 static const double emojiSize = 24 ;
848865
866+ /// Animates the list's scroll position for this item.
867+ ///
868+ /// This serves two purposes when the list is longer than the viewport width:
869+ /// - Ensures the item is in view
870+ /// - By animating, draws attention to the fact that this is a scrollable list
871+ /// and there may be more items in view. (In particular, does this when
872+ /// any item is tapped, because each item has a different [position] .)
849873 void _scrollIntoView (BuildContext context) {
850- Scrollable .ensureVisible (context,
851- alignment: 0.5 , duration: Duration (milliseconds: 200 ));
874+ final scrollPosition = Scrollable .of (context, axis: Axis .horizontal).position;
875+ final destination = lerpDouble (
876+ scrollPosition.minScrollExtent,
877+ scrollPosition.maxScrollExtent,
878+ position);
879+ if (destination == null ) return ; // TODO(log)
880+
881+ scrollPosition.animateTo (destination,
882+ duration: Duration (milliseconds: 200 ),
883+ curve: Curves .ease);
852884 }
853885
854886 void _handleTap (BuildContext context) {
0 commit comments