1
+ import 'dart:ui' ;
2
+
1
3
import 'package:collection/collection.dart' ;
2
4
import 'package:flutter/material.dart' ;
3
- import 'package:flutter/semantics.dart' ;
4
5
5
6
import '../api/exception.dart' ;
6
7
import '../api/model/model.dart' ;
@@ -792,6 +793,19 @@ class ViewReactionsHeader extends StatelessWidget {
792
793
final String ? emojiCode;
793
794
final void Function (ReactionWithVotes ) onRequestSelect;
794
795
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
+
795
809
@override
796
810
Widget build (BuildContext context) {
797
811
final designVariables = DesignVariables .of (context);
@@ -824,9 +838,10 @@ class ViewReactionsHeader extends StatelessWidget {
824
838
explicitChildNodes: true ,
825
839
label: zulipLocalizations.seeWhoReactedSheetHeaderLabel (reactions.total),
826
840
child: Row (
827
- children: reactions.aggregated.map (( r) =>
841
+ children: reactions.aggregated.mapIndexed ((i, r) =>
828
842
_ViewReactionsEmojiItem (
829
843
reactionWithVotes: r,
844
+ position: _emojiItemPosition (i, reactions.aggregated.length),
830
845
selected: r.reactionType == reactionType && r.emojiCode == emojiCode,
831
846
onRequestSelect: onRequestSelect),
832
847
).toList ()))))));
@@ -836,19 +851,36 @@ class ViewReactionsHeader extends StatelessWidget {
836
851
class _ViewReactionsEmojiItem extends StatelessWidget {
837
852
const _ViewReactionsEmojiItem ({
838
853
required this .reactionWithVotes,
854
+ required this .position,
839
855
required this .selected,
840
856
required this .onRequestSelect,
841
857
});
842
858
843
859
final ReactionWithVotes reactionWithVotes;
860
+ final double position;
844
861
final bool selected;
845
862
final void Function (ReactionWithVotes ) onRequestSelect;
846
863
847
864
static const double emojiSize = 24 ;
848
865
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] .)
849
873
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);
852
884
}
853
885
854
886
void _handleTap (BuildContext context) {
0 commit comments