-
Notifications
You must be signed in to change notification settings - Fork 316
Track user status #1629
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?
Track user status #1629
Conversation
Thanks!! CI is failing; could you take a look? |
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, this is exciting! Comments below on the first 4 commits:
5f8cf0e33 api: Add InitialSnapshot.userStatus
1202dbd14 api [nfc]: Add ReactionType.fromApiValue
99b96b76a api: Add user_status event
f333f8935 store: Add UserStore.getUserStatus, with event updates
This is an area where we're reasonably happy with the zulip-mobile implementation, so I recommend reading it. It handles the nuance I mention below, and it discusses the concept of a "zero value" (or perhaps links to discussion; I don't remember 🙂). Could you take a look and see what might be helpful there? For example I think the model code has a name like "user statuses" (plural); it seems like an API wart that the initial snapshot uses the singular user_status
for a map about many users.
lib/api/model/model.dart
Outdated
@@ -158,6 +158,31 @@ class RealmEmojiItem { | |||
Map<String, dynamic> toJson() => _$RealmEmojiItemToJson(this); | |||
} | |||
|
|||
/// An value in [InitialSnapshot.userStatus]. |
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: "A value"
lib/api/model/events.dart
Outdated
static String? _stringFromJson(Object? json) { | ||
final value = json as String; | ||
return value.isNotEmpty ? value : null; | ||
} | ||
|
||
static ReactionType? _reactionTypeFromJson(Object? json) { | ||
final value = json as String; | ||
return value.isNotEmpty ? ReactionType.fromApiValue(value) : null; | ||
} |
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.
There's a nuance that's not really clear in the API doc, but that we figured out and implemented in zulip-mobile:
The meaning of user_status
is really an update to a user's status; it's not a complete representation of what their status is.
In particular, the event's status_text
, emoji_name
, emoji_code
, reaction_type
may be null, and null means that that part of the status didn't change. So for example, if your current status is a house emoji and the text "Working remotely", and we handle an event with status_text: null
and non-null emoji fields, then your status should then be "Working remotely" with the different (not-house) emoji. The fields could be the empty string, which is a "zero value": if the event has status_text: ""
, then that means the status text was cleared (set to "zero"), which is a kind of change.
It's tricky to see this behavior, because I think the current web app always includes all the fields when it makes update-status requests. But some clients do send partial updates (maybe zulip-mobile?), and we should avoid misinterpreting e.g. status_text: null
as clearing out the status text, when in fact it means the status didn't change.
So concretely, for this code, we should allow the fields to be null (so no json as String
), and preserve the distinction between ""
and null
(so no conversion of ""
to null).
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.
Relatedly, we found that user_status
in the initial snapshot isn't a full snapshot of everyone's status either. Each entry represents a change from the baseline of the "zero value" status—
{
away: false, // (which we're ignoring; it's deprecated)
status_text: '',
emoji_name: '',
reaction_type: '',
emoji_code: ''
}
—which explains why a user with that "zero value" status doesn't appear in the initial snapshot.
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.
a2a9b53
to
ae0b51f
Compare
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 the revision! Looks like some existing tests are failing, and build_runner
at one of the first few commits; do you know why that's happening?
@@ -61,6 +61,7 @@ sealed class Event { | |||
default: return UnexpectedEvent.fromJson(json); | |||
} | |||
// case 'muted_topics': … // TODO(#422) we ignore this feature on older servers | |||
case 'user_status': return UserStatusEvent.fromJson(json); |
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.
api: Add user_status event
tools/check build_runner
is failing at this commit; I think that can be fixed with tools/check build_runner --fix
.
The screenshots look great to me! |
ae0b51f
to
d51cc98
Compare
Thank you @chrisbobbe for the previous review. It's good to know about the intricate detail in the API you mentioned. A new revision is pushed with tests included too, PTAL. The CI errors are also solved; thanks for mentioning the suggested fix. |
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! This looks mostly good except I'd like to tighten up a few things:
- I'd like to align a bit more with the model code in zulip-mobile. I think it'll be easier for me to show than to write an explanation, so I'll push a WIP commit at the end. If it makes sense, please incorporate it into the earlier commits.
- In this PR, the status emoji always appears after the user's name, which is a piece of text. Can we use
asWidgetSpan
everywhere, and leave the plainUserStatusEmoji
to be used later, just for #198?- With this pattern, it seems like it would be easier to make sure the appearance stays consistent as new callers are added in the future (and among current callers).
- I think it might mean the status emoji gets cut off sometimes, when a super long name is truncated with "…". Depending on the call site, I'd say either:
- That's OK; the emoji isn't more important than the part of the name we've already decided to cut off. Or,
- The name actually shouldn't be getting cut off (or should be getting cut off later), and let's add a TODO to fix that. (The sender row in the message list, I think; possibly other places?)
If centralizing on asWidgetSpan
makes sense, then the tests will need a way to find the emoji widget inside rich text. Here's a stab at a helper function for that, inspired by similar code for finding UserMention
s in WidgetSpan
s in message content:
/// Find a [UserStatusEmoji] widget, if present, that is shown
/// in a [WidgetSpan] along with a user's name.
///
/// Pass either [fullName] or [fullNameFinder] (not both)
/// to find the [RenderParagraph] that contains the user's name and may
/// contain the [UserStatusEmoji].
///
/// If [fullName] is passed, the following Finder is used:
/// find.textContaining(fullName, findRichText: true)
UserStatusEmoji? findStatusEmojiOnName({String? fullName, Finder? fullNameFinder}) {
assert((fullName == null) != (fullNameFinder == null),
'pass either fullName or fullNameFinder but not both');
fullNameFinder ??= find.textContaining(fullName!, findRichText: true);
final nameRootSpan = tester.renderObject<RenderParagraph>(fullNameFinder).text;
UserStatusEmoji? result;
nameRootSpan.visitChildren((span) {
if (span is! WidgetSpan) return true; // keep looking
result = tester.widgetList<UserStatusEmoji>(
find.descendant(
of: find.byWidget(span.child),
matching: find.byWidgetPredicate((widget) => widget is UserStatusEmoji),
matchRoot: true,
)).singleOrNull;
if (result == null) {
return true; // keep looking
} else {
return false; // found; stop looking
}
});
return result;
}
lib/api/model/initial_snapshot.dart
Outdated
@JsonKey(name: 'user_status') | ||
// In register-queue, the name of this field is the singular "user_status", | ||
// even though it actually contains user status information for all the users | ||
// that the self-user has access to. Therefore, we prefer to use the plural form. |
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: put the comment above the line that makes it necessary, instead of below
lib/api/model/model.dart
Outdated
/// A value in [InitialSnapshot.userStatuses]. | ||
/// | ||
/// For docs, search for "user_status:" | ||
/// in <https://zulip.com/api/register-queue>. | ||
@JsonSerializable(fieldRename: FieldRename.snake) | ||
class UserStatus { | ||
// final bool? away; // deprecated in server-6 (FL-148); ignore | ||
final String? statusText; | ||
final String? emojiName; | ||
final String? emojiCode; | ||
final ReactionType? reactionType; | ||
|
||
UserStatus({ | ||
required this.statusText, | ||
required this.emojiName, | ||
required this.emojiCode, | ||
required this.reactionType, | ||
}); | ||
|
||
factory UserStatus.fromJson(Map<String, dynamic> json) => | ||
_$UserStatusFromJson(json); | ||
|
||
Map<String, dynamic> toJson() => _$UserStatusToJson(this); | ||
} |
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 emoji-status information is contained in multiple fields, and you've already spotted a surprise accident where the fields can be inconsistent with each other. Thanks for that!
From that API design thread, we know how to handle that inconsistency, but ideally we'd have some code that handles it explicitly at the edge / the "crunchy shell", making it possible to write the "soft center" (PerAccountStore
and its consumers) error-free without even needing to be aware of it. I plan to push a draft commit that does that, along with some other suggested changes in how we model the data.
lib/model/user.dart
Outdated
void handleUserStatusEvent(UserStatusEvent event) { | ||
final UserStatusEvent( | ||
:userId, :statusText, :emojiName, :emojiCode, :reactionType) = event; | ||
|
||
final oldStatus = _userStatuses[userId]; | ||
// Here's what the different values of a property in the event mean and how | ||
// they affect the corresponding values in the resulting new status: | ||
// - Value is `null` -> property is not changed -> value in the new status | ||
// is the same as in the old status, or `null` if status wasn't set before. | ||
// - Value is empty -> property is cleared -> value in the new status is `null`. | ||
// - Value is not empty -> property is set - value in the new status is the | ||
// same as the value from the event. | ||
final newStatus = UserStatus( | ||
statusText: statusText == null | ||
? oldStatus?.statusText | ||
: statusText.isEmpty | ||
? null | ||
: statusText, | ||
emojiName: emojiName == null | ||
? oldStatus?.emojiName | ||
: emojiName.isEmpty | ||
? null | ||
: emojiName, | ||
emojiCode: emojiCode == null | ||
? oldStatus?.emojiCode | ||
: emojiCode.isEmpty | ||
? null | ||
: emojiCode, | ||
reactionType: reactionType == null | ||
? oldStatus?.reactionType | ||
: reactionType == UserStatusEventReactionType.empty | ||
// Currently, if a status emoji is not selected, the server sends | ||
// "reaction_type: unicode_emoji", so to keep things clean, | ||
// we treat it as `null`. | ||
// See discussion: | ||
// https://chat.zulip.org/#narrow/channel/378-api-design/topic/user.20status/near/2203928 | ||
|| (emojiName!.isEmpty) | ||
? null | ||
: ReactionType.fromApiValue(reactionType.toJson()), | ||
); | ||
|
||
_userStatuses[event.userId] = newStatus; | ||
} |
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 can become much shorter with a change in how we model the data; I plan to push a draft commit to demonstrate, as mentioned :)
(And the "keep things clean" logic will be out at the "crunchy shell", as I hinted in a previous comment.)
lib/widgets/emoji.dart
Outdated
/// Whether to disable the animation for animated emojis. | ||
/// | ||
/// By default, animation is enabled. | ||
final bool noAnimation; |
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.
Let's improve this by making it clear that it's an override, that it's not making the caller responsible for checking the accessibility settings, power-saving settings, etc.
How about something like:
/// Whether to show an animated emoji in its "still" variant only,
/// even if device settings permit animation.
///
/// Defaults to false.
final bool neverAnimate;
lib/widgets/content.dart
Outdated
return switch (emojiDisplay) { | ||
UnicodeEmojiDisplay() => UnicodeEmojiWidget( | ||
size: size, notoColorEmojiTextSize: notoColorEmojiTextSize, | ||
emojiDisplay: emojiDisplay), | ||
ImageEmojiDisplay() => ImageEmojiWidget( | ||
size: size, emojiDisplay: emojiDisplay, noAnimation: noAnimation), | ||
// If the status emoji has to be displayed as text because it failed to | ||
// load, we ignore it because it would look weird for emojis with longer | ||
// text, in certain places, for example in message list. | ||
TextEmojiDisplay() => SizedBox.shrink(), | ||
}; |
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 this handles the case where an image emoji's URL doesn't parse, but it doesn't yet handle the case where the network request for the image fails. How about:
// If an image emoji's URL string doesn't parse or the image fails to load,
// show nothing. The user-status feature doesn't support a :text_emoji:-
// style display.
final placeholder = SizedBox.shrink();
return switch (emojiDisplay) {
UnicodeEmojiDisplay() => UnicodeEmojiWidget(
size: size, notoColorEmojiTextSize: notoColorEmojiTextSize,
emojiDisplay: emojiDisplay),
ImageEmojiDisplay() => ImageEmojiWidget(
size: size,
emojiDisplay: emojiDisplay,
neverAnimate: noAnimation,
errorBuilder: (_, _, _) => placeholder,
),
TextEmojiDisplay() => placeholder,
};
@@ -182,6 +186,22 @@ void main() { | |||
} | |||
} | |||
|
|||
void checkStatusEmoji({required bool isVisible, int? count}) { |
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.
recent-dms: Show user status emoji in recent DMs page
It looks like count
is never passed; can we simplify that away?
lib/widgets/content.dart
Outdated
static InlineSpan asWidgetSpan({ | ||
required int userId, | ||
required double size, | ||
required double notoColorEmojiTextSize, | ||
bool noAnimation = true, | ||
}) { | ||
return WidgetSpan( | ||
alignment: PlaceholderAlignment.middle, | ||
child: Padding( | ||
padding: const EdgeInsetsDirectional.only(start: 4.0), | ||
child: UserStatusEmoji(userId: userId, size: size, | ||
notoColorEmojiTextSize: notoColorEmojiTextSize, noAnimation: noAnimation))); | ||
} |
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.
Could you add a dartdoc with some guidance helping callers decide whether to use asWidgetSpan
vs. just the UserStatusEmoji
constructor? As long as we're putting the status emoji directly after text, it seems like asWidgetSpan
is a possible choice, but I don't know whether it's always or only sometimes the right choice. Having the sizing, spacing, and vertical alignment always look right is a goal 🙂
We'll probably need the not-WidgetSpan
variant for at least the choose-emoji-status picker, #198, later, but I'm not sure about the sites in this PR.
final statusEmojiFinder = find.ancestor(of: find.byType(UnicodeEmojiWidget), | ||
matching: find.byType(UserStatusEmoji)); |
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 that sometimes the status emoji won't be a Unicode emoji; here and one more below)
test/widgets/new_dm_sheet_test.dart
Outdated
check(findUserChip(user1)).findsNothing(); | ||
check(findUserChip(user2)).findsNothing(); | ||
checkChipStatusEmoji(user1, isPresent: false); | ||
checkChipStatusEmoji(user2, isPresent: false); |
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.
These checkChipStatusEmoji
calls are redundant with the findUserChip(…).findsNothing()
s.
final statusEmojiFinder = find.ancestor(of: find.byType(UnicodeEmojiWidget), | ||
matching: find.byType(UserStatusEmoji)); |
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 that some status emoji won't be Unicode emoji)
lib/api/model/events.dart
Outdated
/// representing an empty string value in [UserStatusEvent] where no status | ||
/// emoji is selected. | ||
@JsonEnum(fieldRename: FieldRename.snake) | ||
enum UserStatusEventReactionType { |
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 made sense to add, before I thought of the changes in my WIP commit 🙂 but I think the WIP commit gives a nice way to make this unnecessary, in addition to aligning more with the intended API semantics.
lib/api/model/model.dart
Outdated
if (reactionType == null || emojiCode == null || emojiName == null) { | ||
return null; | ||
} else if (reactionType == '' || emojiCode == '' || emojiName == '') { |
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.
Can the null case actually happen? That seems at odds with the API docs:
https://zulip.com/api/get-events#user_status
which say these fields are always present, though sometimes the empty string.
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.
Hmm. We intentionally wrote zulip-mobile to handle the fields being sometimes absent; see type UserStatusUpdate
and linked discussion in zulip/zulip-mobile@2106381e4
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.
those should eventually make it into the server API doc
Hmm I see :-)
lib/api/model/model.dart
Outdated
static StatusTextChange? fromApiValue(String? apiValue) => | ||
switch (apiValue) { | ||
null => null, | ||
'' => StatusTextChange(null), | ||
_ => StatusTextChange(apiValue), |
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.
Similarly, can this null case actually happen?
lib/api/model/model.dart
Outdated
/// The absence of one of these means there is no change. | ||
class StatusTextChange { | ||
final String? newValue; | ||
const StatusTextChange(this.newValue); |
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.
These two classes, and their apply
methods, feel fairly boilerplatey to me. I think what's going on can be made clearer by using a generic "option"/"maybe" type. I've just pushed some additional commits as a demo of that approach.
Other than the commit adding Option, those commits are to be squashed into the "WIP add/use UserStatusChange" commit, and/or into the preceding commits that that one would get squashed into.
e4dbf32
to
11ba820
Compare
OK, let's go with the UserStatusChange approach mentioned at #1629 (review) . I've just pushed a revision which
The Option commit should get reordered to earlier in the branch, and the other commit incorporated as Chris mentioned above. |
About |
11ba820
to
89b10fa
Compare
Thanks @chrisbobbe and @gnprice for the reviews. I have pushed the new revision, PTAL. I haven't investigated the suggested solution in #1629 comment yet. I will do that after I get some sleep now. Edit: The result of the investigation is shared in #1629 (comment).
|
42dec2d
to
8d30ccd
Compare
Ok, so the CI failure is solved. The following change had to be made: class InitialSnapshot {
...
- @JsonKey(name: 'user_status', includeToJson: false)
+ @JsonKey(name: 'user_status')
final Map<int, UserStatusChange> userStatuses;
...
} In addition to adding |
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! I think this is close; comments below, and I'll also ask @gnprice to take a look too in case it's almost ready for our release today (or in case he has thoughts on the emoji-size stuff, and lib/api/model tests).
lib/api/model/model.dart
Outdated
} else if (reactionType == '' || emojiCode == '' || emojiName == '') { | ||
// Sometimes `reaction_type` is 'unicode_emoji' when the emoji is cleared. | ||
// This is an accident, to be handled by looking at `emoji_code` instead: | ||
// https://chat.zulip.org/#narrow/channel/378-api-design/topic/user.20status/near/2203132 | ||
return OptionSome(null); |
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.
api: Add InitialSnapshot.userStatuses
Ideally we'd have a test that this server glitch is tolerated, and for the rest of the nontrivial logic in this commit and the commit that adds the event type. Perhaps this can be a followup; @gnprice, what do you think? 🙂
As discussed in a previous review, it's great that we're handling the glitch just once, here at the edge/"crunchy shell". Since we don't let the glitch affect anything deeper in the app code, we don't have to write tests against this glitch in all the places we consume the data 🙂
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.
Tests are added for the logic in this commit, but for the commit that adds the event type, I think the tests added in 8e7b47a
- "store: Add UserStore.getUserStatus, with event updates" cover them. 🙂
await store.handleEvent(userStatusEvent((5, | ||
'Working remotely', '', '', ''))); | ||
checkUserStatus(store.getUserStatus(5), | ||
('Working remotely', null, null, null)); |
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.
Let's add another stanza like this, checking what happens when the event says both the text and emoji were cleared.
lib/widgets/message_list.dart
Outdated
Padding( | ||
padding: const EdgeInsetsDirectional.only(start: 5.0), | ||
child: UserStatusEmoji(userId: sender.userId, | ||
size: 18, notoColorEmojiTextSize: 15), |
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 investigating! (#1629 (comment))
What was your experiment that showed "it worked perfectly fine"? 🙂 I'm imagining something like this:
- (Make sure you're testing on Android, with Unicode emoji, since
notoColorEmojiTextSize
has no effect on iOS or with image emoji.) - Choose a
size
. - Multiply that
size
by(17.0 / 14.5)
and try the result fornotoColorEmojiTextSize
. - Open the dev tools to "select widget mode" and confirm that the emoji in fact occupies a
size
-sized square.
If that's successful for several size
values and a particular line-height setting, then it sounds like the widget can compute notoColorEmojiTextSize
for itself (using size * (17.0 / 14.5)
) as long as it always uses that same line-height setting. From reading the dartdoc it does sound like the actual resulting size can be different for different line-height settings, so we should either:
- have the implementation use a constant line height (explicitly overriding a value that might be in an ancestor
DefaultTextStyle
etc.), or - satisfy ourselves that the dartdoc is wrong and fix it. 🙂
- (Or I guess give the widget a line-height param and adjust from that, if we can learn exactly how the line height affects the size.)
See the doc on line heights: https://api.flutter.dev/flutter/painting/TextStyle/height.html
What was the effective line in your testing on the message-list page? I think you can find it in the dev tools:
(Things like Material defaults from Typography
and our own DefaultTextStyle
s might be controlling it, I don't remember all the details offhand.)
lib/widgets/message_list.dart
Outdated
Padding( | ||
padding: const EdgeInsetsDirectional.only(start: 5.0), | ||
child: UserStatusEmoji(userId: sender.userId, | ||
size: 18, notoColorEmojiTextSize: 15), |
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's OK to leave that refactor for later if it's complicated. I wouldn't want to merge something that accidentally makes us forget how to show emojis at a size from our design specs. 🙂
But the PR should at least maintain the invariant that each emoji's actual size matches size
. In the current world before the refactor, that'll involve opening the devtools to check the size against size
for all the new callsites in the PR, unless you're sure that a given callsite uses the same size
and line height as one you've already confirmed experimentally.
test/widgets/message_list_test.dart
Outdated
final senderRowFinder = find.ancestor( | ||
of: nameFinder, | ||
matching: find.ancestor( | ||
of: statusEmojiFinder, | ||
matching: find.byType(Row), | ||
), | ||
); |
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: can make this more compact, putting parens on the same line
lib/widgets/new_dm_sheet.dart
Outdated
child: Text.rich( | ||
TextSpan( | ||
children: [ | ||
TextSpan(text: store.userDisplayName(userId)), | ||
UserStatusEmoji.asWidgetSpan(userId: userId, size: 17, | ||
notoColorEmojiTextSize: 14), | ||
] | ||
), |
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: can be more compact with parens/newlines
lib/widgets/autocomplete.dart
Outdated
Row( | ||
children: [ | ||
Flexible(child: labelWidget), | ||
if (option case UserMentionAutocompleteResult(:var userId)) | ||
Padding( | ||
padding: const EdgeInsetsDirectional.only(start: 5.0), | ||
child: UserStatusEmoji( | ||
userId: userId, | ||
size: 18, | ||
notoColorEmojiTextSize: 15), | ||
) | ||
], | ||
), |
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: make more compact
I'm just about to prepare today's release, so this is just in time for me to include it as a not-yet-merged PR 🙂. I skimmed the code and also played a bit with the UI, and it looks ready for that. Then that'll give time to discuss the questions above, and otherwise review a bit more, before merging early next week. |
3daa158
to
f6d3bf0
Compare
Co-authored-by: Greg Price <[email protected]> Co-authored-by: Chris Bobbe <[email protected]>
Co-authored-by: Chris Bobbe <[email protected]>
Co-authored-by: Chris Bobbe <[email protected]>
Co-authored-by: Chris Bobbe <[email protected]>
Co-authored-by: Chris Bobbe <[email protected]>
Status emojis are only shown for self-1:1 and 1:1 conversation items. They're ignored for group conversations as that's what the Web does.
f6d3bf0
to
c8bc5dd
Compare
Thanks @chrisbobbe for the review. New revision pushed with tests added for #1629 (comment). |
Support for tracking the user status.
Message List:
Recent DMs Page:
New DM Sheet
User Autocomplete:
Profile Page:
Fixes: #197