-
Notifications
You must be signed in to change notification settings - Fork 320
msglist: Support viewing who reacted to a message #1700
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
base: main
Are you sure you want to change the base?
Conversation
Thanks! I skimmed through this and I'm pretty confident it won't break any existing functionality. I also saw you demonstrate this running on your device, and it seemed to work well. So I plan to include this in today's upcoming release, without yet merging to main. |
27dd265
to
d0f7d15
Compare
Thanks! This is now ready for review, and I've updated the issue description with changes since the draft. |
This fixes the two inconsistencies flagged in discussion: https://chat.zulip.org/#narrow/channel/530-mobile-design/topic/bottom.20sheet.20.22Cancel.22.2F.22Close.22.20button/near/2216116 > I think it's reasonable to have both labels, but I think we should > choose them differently than now: > > - "Cancel" when the sheet is about doing an action: [etc.] > > - "Close" when the sheet just presents information or nav options: > [etc.]
…s test So we can add another test that uses it.
d0f7d15
to
399cd6a
Compare
Thanks! Would you also post a few screenshots? |
I experimented with using Semantics to help write human-centered tests, and I ended up adding some configuration that actually seemed to make a reasonable experience in the UI, at least in my testing with VoiceOver. Fixes zulip#740.
399cd6a
to
e653144
Compare
Sure! Done. |
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!
Generally this looks good. Here's a full review except the tests.
// to the underlying Scrollable to remove an unwanted node | ||
// in accessibility focus traversal. | ||
scrollDirection: Axis.horizontal, | ||
physics: ClampingScrollPhysics(), |
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.
Interesting — why this instead of the default?
(I believe the default would be this on Android, but BouncingScrollPhysics
on iOS.)
messageId: widget.messageId, | ||
reactionType: reactionType, | ||
emojiCode: emojiCode, | ||
onRequestSelect: (r) => _setSelection(r), |
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: tearoff
onRequestSelect: (r) => _setSelection(r), | |
onRequestSelect: _setSelection, |
|
||
/// Check that the given reaction still has votes; | ||
/// if not, select a different one if possible or clear the selection. | ||
void _reconcile() { |
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.
It looks like this always concludes with _setSelection
. It might be a little clearer to read by making that explicit up front:
void _reconcile() { | |
void _reconcile() { | |
_setSelection(_findMatchingReaction()); | |
} |
Then _findMatchingReaction can directly return whenever it's found its answer.
if (reactionType == null && widget.initialReactionType != null) { | ||
assert(emojiCode == null); | ||
assert(widget.initialEmojiCode != null); | ||
reactionType = widget.initialReactionType!; | ||
emojiCode = widget.initialEmojiCode!; |
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 doesn't depend on the store, does it? It looks like it doesn't use the context at all; so it could run as early as initState.
And conversely it doesn't sound like it'd be desirable for this to run repeatedly, or after a new store — these are the initial emoji type and code, after all.
return SizedBox( | ||
width: double.infinity, | ||
child: Column( | ||
mainAxisSize: MainAxisSize.min, |
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.
Would this be equivalent?
return SizedBox( | |
width: double.infinity, | |
child: Column( | |
mainAxisSize: MainAxisSize.min, | |
return Column( | |
mainAxisSize: MainAxisSize.min, | |
crossAxisAlignment: CrossAxisAlignment.stretch, |
(x) => x.reactionType == reactionType && x.emojiCode == emojiCode | ||
)?.userIds.toList(); | ||
|
||
// (No filtering of muted or deactivated users.) |
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.
// (No filtering of muted or deactivated users.) | |
// (No filtering of muted or deactivated users. | |
// Muted users will be shown as muted.) |
My first reaction to this comment was "is that right? seems like we shouldn't show muted users here." Then I remembered that that's probably handled at a different layer, and indeed it looks like it is.
Widget result = InsetShadowBox( | ||
top: 8, bottom: 8, | ||
color: designVariables.bgContextMenu, | ||
child: SizedBox( | ||
height: 400, // TODO(design) tune | ||
child: ListView.builder( | ||
padding: EdgeInsets.symmetric(vertical: 8), |
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: move SizedBox outward, so the InsetShadowBox is right next to (in the source code) the padding that needs to match it
itemBuilder: (context, index) => | ||
ViewReactionsUserItem(context, userId: userIds[index])))); |
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.
Same comment as I told Sayed last week 🙂 : #1706 (comment)
(on code that I guess was modeled in part on this PR, so no coincidence)
Navigator.pop(pageContext); | ||
|
||
Navigator.push(pageContext, | ||
ProfilePage.buildRoute(context: pageContext, userId: userId)); |
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 think the reason this works even though this pageContext
isn't really a page context is that there's no await
here.
The reason our action-sheet buttons often need a real page context is that they'll dismiss the action sheet, then do something that takes time like a network request, and then after that completes they need to use a context to do something with the result, or to show an error. So if the request takes longer than the few hundred ms of the dismiss animation, the action sheet's own context may be unmounted by that point.
For just synchronously acting on the navigation like this, the widget's own context is fine.
onTap: _onPressed, | ||
splashFactory: NoSplash.splashFactory, | ||
overlayColor: WidgetStateColor.resolveWith((states) => | ||
states.any((e) => e == WidgetState.pressed) |
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.
states.any((e) => e == WidgetState.pressed) | |
states.contains(WidgetState.pressed) |
Looks like it's a Set, so that should be equivalent (and potentially faster)
Fixes #740.
Followup:
Notable changes from previous revision:
Screenshots
I've included some screenshots where there are
and in those, I set the scroll position such that you can see the shadow effect.
Scroll-on-select animation: