-
Notifications
You must be signed in to change notification settings - Fork 327
Make read-receipts and view-reactions draggable-scrollable #1802
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make read-receipts and view-reactions draggable-scrollable #1802
Conversation
Finger down on header, move up and downBefore: ScreenRecording_08-14-2025.12-55-18_1.movAfter: ScreenRecording_08-14-2025.12-57-41_1.movFinger down in list, move up and downBefore: ScreenRecording_08-14-2025.12-56-03_1.movAfter: ScreenRecording_08-14-2025.12-58-02_1.mov |
I can record the view-reactions behavior too, but it's similar, and anyway the best way to see the behavior is to try it yourself :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for working on this @chrisbobbe! This works great on my device, and LGTM.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Neat, thanks for working this out.
Generally this all looks good; just small comments.
lib/widgets/action_sheet.dart
Outdated
/// Simply a [BottomSheetEmptyContentPlaceholder] wrapped in | ||
/// [SliverToBoxAdapter]. | ||
class SliverBottomSheetEmptyContentPlaceholder extends StatelessWidget { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like this might be clearer inlined, then — less than a line's worth of extra code, and more transparent.
lib/widgets/action_sheet.dart
Outdated
// The "inset shadow" effect in Figma is a bit awkwardly | ||
// implemented and there might be a better factoring: | ||
// 1. This effect leans on the abstraction that [contentSliver] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// The "inset shadow" effect in Figma is a bit awkwardly | |
// implemented and there might be a better factoring: | |
// 1. This effect leans on the abstraction that [contentSliver] | |
// The "inset shadow" effect in Figma is a bit awkwardly | |
// implemented here and there might be a better factoring: | |
// 1. This effect leans on the abstraction that [contentSliver] |
Is that the intended meaning? My first thought was this line was about what's in Figma.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Eep, yeah, thanks for flagging.
physics: ClampingScrollPhysics(), | ||
controller: controller, | ||
slivers: [ | ||
PinnedHeaderSliver(child: headerWithShadow), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've managed it here, modulo with a header instead of a drag handle,
by making sure the scrollable area extends through the top of the
sheet. (Done with a CustomScrollView, pinning the header to the
viewport top.)
Neat!
lib/widgets/emoji_reaction.dart
Outdated
final userIds = message?.reactions?.aggregated.firstWhereOrNull( | ||
(x) => x.reactionType == reactionType && x.emojiCode == emojiCode | ||
)?.userIds.toList(); | ||
|
||
Widget contentSliver; | ||
if (reactionType != null && emojiCode != null && userIds != null) { | ||
assert(emojiName != null); | ||
|
||
// (No filtering of muted or deactivated users. | ||
// Muted users will be shown as muted.) | ||
|
||
contentSliver = SliverList.builder( | ||
itemCount: userIds.length, | ||
itemBuilder: (_, index) => ViewReactionsUserItem(userId: userIds[index])); | ||
|
||
contentSliver = SliverSemantics( | ||
identifier: ViewReactions.semanticsIdentifier, // See note on `controlsNodes` on the tab. | ||
label: zulipLocalizations.seeWhoReactedSheetUserListLabel(emojiName!, userIds.length), | ||
role: SemanticsRole.tabPanel, | ||
container: true, | ||
explicitChildNodes: true, | ||
sliver: contentSliver); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This part feels like it would be useful to continue to have in its own widget, like ViewReactionsUserList.
Could put "sliver" in the name of the widget to make clear its render object will be a sliver rather than a box.
e32e34f
to
a9b5745
Compare
Thanks for the review, revision pushed! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Looks good; just a couple of nits.
lib/widgets/action_sheet.dart
Outdated
final backgroundColor = Theme.of(context).bottomSheetTheme.backgroundColor!; | ||
|
||
// The "inset shadow" effect in Figma is a bit awkwardly | ||
// implemented here and there might be a better factoring: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
// implemented here and there might be a better factoring: | |
// implemented here, and there might be a better factoring: |
I know I suggested this wording myself 😅 yesterday above, but I misparsed this on first reading today, with the phrase "here and there".
lib/widgets/emoji_reaction.dart
Outdated
if (userIds == null) { | ||
// This reaction lost all its votes, or the message was deleted. | ||
return SizedBox.shrink(); | ||
return SliverPadding(padding: EdgeInsets.zero); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this is also now the condition that handles the case where reactionType and/or emojiCode is null.
It'd be good to make that explicit, at least by editing the comment. Might be clearer still to put an explicit condition for it at the top of the build method.
a9b5745
to
a2774bb
Compare
Thanks! Revision pushed. |
There are other localization classes, like MaterialLocalizations, and in any case it seems right to have a consistent naming pattern.
The Figma asks that the sheet be expandable to fill the screen: https://www.figma.com/design/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=11367-21131&m=dev and that's implemented here. Compare f8ddff2, where we removed an earlier implementation. I hadn't tried to bring that back yet because I wanted to support triggering resize from a drag handle at the top, and I couldn't figure out how to do that. IIRC I could only get the drag handle to respond to drag-down gestures (via `enableDrag: true`) by shifting the sheet's position downward. That worked as a way to dismiss the sheet, but it was frustratingly different from the gesture handling on the scrollable list: - The slide-to-dismiss looked different from the shrink-and-dismiss, an awkward inconsistency - The list would respond to upward drags, too (by growing and showing more content) I've managed it here, modulo with a header instead of a drag handle, by making sure the scrollable area extends through the top of the sheet. (Done with a CustomScrollView, pinning the header to the viewport top.)
This fixes a test failure when we make an upcoming refactor. (The container node's label would apparently have its children's labels appended to it...I haven't seen that behavior in manual testing on my iPhone with VoiceOver, before or after that refactor, but, shrug; this change does seem correct anyway.)
…tions Like we did for read-receipts in a recent commit. I *think* this behavior is implied in the Figma, but it's not as explicit: https://www.figma.com/design/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=5878-19207&m=dev ...anyway, helpful to be consistent with read-receipts, I think, which is similarly a read-only view that might have a long list to show. I thought this would be more complicated than it turned out to be -- thanks to SliverSemantics, I can actually wrap the list of voters in a labeled container node, because DraggableScrollableModalBottomSheet's API takes a sliver for the content.
a2774bb
to
d9ab075
Compare
Thanks! Looks good; merging. |
For manual testing, ideally use a real device, to see the fling and drag responses faithfully to what most users will experience :)
Screenshots coming soon.
Selected commit messages:
ead_receipts: Improve UX by making the sheet draggable-scrollable
The Figma asks that the sheet be expandable to fill the screen:
https://www.figma.com/design/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=11367-21131&m=dev
and that's implemented here.
Compare f8ddff2, where we removed an earlier implementation. I
hadn't tried to bring that back yet because I wanted to support
triggering resize from a drag handle at the top, and I couldn't
figure out how to do that. IIRC I could only get the drag handle to
respond to drag-down gestures (via
enableDrag: true
) by shiftingthe sheet's position downward. That worked as a way to dismiss the
sheet, but it was frustratingly different from the gesture handling
on the scrollable list:
an awkward inconsistency
showing more content)
I've managed it here, modulo with a header instead of a drag handle,
by making sure the scrollable area extends through the top of the
sheet. (Done with a CustomScrollView, pinning the header to the
viewport top.)
emoji_reaction: Use DraggableScrollableModalBottomSheet for view-reactions
Like we did for read-receipts in a recent commit. I think this
behavior is implied in the Figma, but it's not as explicit:
https://www.figma.com/design/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=5878-19207&m=dev
...anyway, helpful to be consistent with read-receipts, I think,
which is similarly a read-only view that might have a long list to
show.
I thought this would be more complicated than it turned out to be --
thanks to SliverSemantics, I can actually wrap the list of voters in
a labeled container node, because
DraggableScrollableModalBottomSheet's API takes a sliver for the
content.