Skip to content

Commit 1d12c74

Browse files
committed
emoji_reaction: Refine scroll-into-view logic
For motivation, see the comment on _scrollIntoView.
1 parent 59eb6b8 commit 1d12c74

File tree

1 file changed

+35
-4
lines changed

1 file changed

+35
-4
lines changed

lib/widgets/emoji_reaction.dart

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import 'dart:ui';
2+
13
import 'package:collection/collection.dart';
24
import 'package:flutter/material.dart';
3-
import 'package:flutter/semantics.dart';
45

56
import '../api/exception.dart';
67
import '../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 {
841856
class _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

Comments
 (0)