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' ;
@@ -797,6 +798,19 @@ class ViewReactionsHeader extends StatelessWidget {
797
798
final String ? emojiCode;
798
799
final void Function (ReactionWithVotes ) onRequestSelect;
799
800
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
+
800
814
@override
801
815
Widget build (BuildContext context) {
802
816
final designVariables = DesignVariables .of (context);
@@ -829,9 +843,10 @@ class ViewReactionsHeader extends StatelessWidget {
829
843
explicitChildNodes: true ,
830
844
label: zulipLocalizations.seeWhoReactedSheetHeaderLabel (reactions.total),
831
845
child: Row (
832
- children: reactions.aggregated.map (( r) =>
846
+ children: reactions.aggregated.mapIndexed ((i, r) =>
833
847
_ViewReactionsEmojiItem (
834
848
reactionWithVotes: r,
849
+ position: _emojiItemPosition (i, reactions.aggregated.length),
835
850
selected: r.reactionType == reactionType && r.emojiCode == emojiCode,
836
851
onRequestSelect: onRequestSelect),
837
852
).toList ()))))));
@@ -841,19 +856,35 @@ class ViewReactionsHeader extends StatelessWidget {
841
856
class _ViewReactionsEmojiItem extends StatelessWidget {
842
857
const _ViewReactionsEmojiItem ({
843
858
required this .reactionWithVotes,
859
+ required this .position,
844
860
required this .selected,
845
861
required this .onRequestSelect,
846
862
});
847
863
848
864
final ReactionWithVotes reactionWithVotes;
865
+ final double position;
849
866
final bool selected;
850
867
final void Function (ReactionWithVotes ) onRequestSelect;
851
868
852
869
static const double emojiSize = 24 ;
853
870
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] .)
854
878
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);
857
888
}
858
889
859
890
void _handleTap (BuildContext context) {
0 commit comments