Skip to content

Commit 45ad6ab

Browse files
committed
emoji_reaction: Refine scroll-into-view logic
For motivation, see the comment on _scrollIntoView.
1 parent a535d2a commit 45ad6ab

File tree

1 file changed

+36
-4
lines changed

1 file changed

+36
-4
lines changed

lib/widgets/emoji_reaction.dart

Lines changed: 36 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';
@@ -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 {
836851
class _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

Comments
 (0)