Skip to content
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

user-status: Add UI for setting emoji status #5277

Merged
merged 20 commits into from
Mar 9, 2022

Conversation

chrisbobbe
Copy link
Contributor

And a few other UI improvements there, and a new "Busy" option for #5255.

Still TODO: making the status emoji actually show up where we want it to :)

Fixes: #5255

@chrisbobbe chrisbobbe requested a review from gnprice March 5, 2022 02:18
@chrisbobbe chrisbobbe marked this pull request as draft March 5, 2022 02:22
Copy link
Member

@gnprice gnprice left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @chrisbobbe! Generally this looks good. Comments below.

As I mentioned in the office on Friday, before merging I'd like to see the emoji status appear in at least one place in the UI: the profile screen that you get to this screen from, because otherwise when you complete this interaction it looks like setting the emoji didn't work. But I'd be happy to leave the rest of the UI for a separate PR.

import type { EmojiType } from '../types';
import { createStyleSheet, BORDER_COLOR } from '../styles';

export type Value = null | {| +code: string, +name: string, +type: EmojiType |};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: type before code, because the code lives in a namespace determined by the type

(and then same thing where constructing these in UserStatusScreen.js)

Comment on lines 85 to 99
const { navigation } = props;

const zulipFeatureLevel = useSelector(getZulipFeatureLevel);
const auth = useSelector(getAuth);
const ownUserId = useSelector(getOwnUserId);
const userStatusText = useSelector(state => getUserStatus(state, ownUserId).status_text);
const userStatusEmoji = useSelector(state => getUserStatus(state, ownUserId).status_emoji);

const [statusText, setStatusText] = useState<string | null>(userStatusText);
const [textInputValue, setTextInputValue] = useState<string>(
inputValueFromStatusText(userStatusText),
);
const [emojiInputValue, setEmojiInputValue] = useState<EmojiInputValue>(
inputValueFromStatusEmoji(userStatusEmoji),
);
const _ = useContext(TranslationContext);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the organization of stanzas here: props, selectors, state, (then below this) callbacks.

A nit: the useContext fits best in the selectors stanza, rather than state. (Like the selectors, it's basically pulling some state from some ancestor component near the top of the React tree.)

Comment on lines +64 to +74
...(Platform.OS === 'ios'
? {
borderWidth: 1,
borderColor: BORDER_COLOR,
borderRadius: 2,
padding: 8,
}
: Object.freeze({})),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm interesting. Why are the border and padding only on iOS?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah right, this needs a comment (and should possibly change). It's meant to mimic Input, which has

const componentStyles = createStyleSheet({
  input: {
    ...Platform.select({
      ios: {
        borderWidth: 1,
        borderColor: BORDER_COLOR,
        borderRadius: 2,
        padding: 8,
      },
    }),
  },
});

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, got it. IIRC that conditional is there in order to conform to platform conventions. This UI might call for something else, though.

Comment on lines 50 to 51
button: {
flex: 1,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this flex: 1 still doing something? I'd guess that it should be deleted when the enclosing "button wrapper" View is deleted -- the one that had flexDirection: 'row' because there were two buttons sharing the horizontal space.

@@ -241,7 +241,6 @@
"Share failed": "Share failed",
"Download complete": "Download complete",
"Download file": "Download file",
"Update": "Update",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At a grep, it looks like we still have this string elsewhere:

$ rg -w Update src/
src/streams/EditStreamCard.js
91:          text={isNewStream ? 'Create' : 'Update'}

Comment on lines 8 to 9
// This is a separate file due to Prettier having issues with formatting unicode
// Using `prettier-ignore` does not help.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, seems like this comment stops applying.

If so, we can also fold this file into UserStatusScreen.js.

Comment on lines 165 to 168
title={
zulipFeatureLevel >= 86
? `${parseUnicodeEmojiCode(emoji.emoji_code)} ${translatedText}`
: text
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, doesn't seem right that this gets translated or not depending on the server feature level.

… Huh, were these actually getting translated before? … Looks like not, when I test it just now.

I think this should say translatedText in the "else" case. And perhaps a good prep commit would be to start translating, which I think is nicely orthogonal to using status emoji.

<SelectableOptionRow
itemKey={index}
title={
zulipFeatureLevel >= 86
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably good to pull this comparison out as a local (of the render function, up by the selectors), like const serverSupportsEmojiStatus = …. Then that can get a TODO-server comment.

Comment on lines +175 to +193
setTextInputValue(translatedText);
setEmojiInputValue(inputValueFromStatusEmoji(emoji));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If emoji statuses aren't supported…we do end up just cutting the
emoji out of the status suggestion, which I guess is a small
downside. For these older servers, we could have kept up what we do
now: stick the status emoji at the start of the suggested text. But
I think achieving that has a higher complexity cost than it's worth.
I've never really used this UI, I'm not sure how common it is to do
so, and people are still free to depart from the suggested text,
including by adding emojis, by using the text input.

Yeah, agreed.

'🏠 Working remotely',
];
const items: $ReadOnlyArray<Item> = [
[{ emoji_name: 'working_on_it', emoji_code: '1f6e0' }, 'Busy'],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's get these codes from unicodeCodeByName. That way there isn't a risk of the name and code disagreeing with each other, or getting out of sync if our names change.

chrisbobbe added a commit to chrisbobbe/zulip-mobile that referenced this pull request Mar 8, 2022
@chrisbobbe
Copy link
Contributor Author

chrisbobbe commented Mar 8, 2022

Thanks for the review! Revision pushed.

As I mentioned in the office on Friday, before merging I'd like to see the emoji status appear in at least one place in the UI: the profile screen that you get to this screen from, because otherwise when you complete this interaction it looks like setting the emoji didn't work. But I'd be happy to leave the rest of the UI for a separate PR.

Makes sense. I put a commit to show the emoji status on that profile screen you're talking about (AccountDetails) before the commits that let the user change their emoji status, so we don't have any commits that seem broken in the way you describe.

Then, at the end of the branch, I've added commits for displaying emoji status in other places, so those commits can easily be split off into another PR.

Copy link
Member

@gnprice gnprice left a 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! Two small comments, and then I haven't yet read the five new commits at the end:

205fe61 UserItem: Show emoji status, if set
85e3a33 user-statuses model: Export getUserStatuses after all
e34896b msglist: Show sender's emoji status, if set
2379d97 msglist tests: Use more of our stock example data
5aa031f msglist tests: Add snapshots for sender emoji statuses

First I'll push a quick revision as mentioned below.

Comment on lines 101 to 102
const { navigation } = props;
const _ = useContext(TranslationContext);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UserStatusScreen [nfc]: Move `useContext` upward, with props

As Greg suggests:
  https://github.com/zulip/zulip-mobile/pull/5277#discussion_r820978534

Just to be sure you saw: my suggestion was actually to put it with the selectors, not with the props. But here with the props is fine too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, indeed! I've gone and put it with the selectors for the next revision.

Comment on lines 157 to 163
<SelectableOptionRow
key={item}
itemKey={item}
title={item}
title={_(item)}
selected={item === statusTextFromInputValue(textInputValue)}
onRequestSelectionChange={itemKey => {
setTextInputValue(_(itemKey));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UserStatusScreen: Translate status-text suggestions

Huh, I guess we hadn't been calling _, even though the strings
appear in messages_en.json.

Yeah. I think in fact it's buggier than that -- the setTextInputValue call does translate. So you have an option that says "📅 In a meeting", even though you've chosen a UI language of say German; but then you select it and the text input gets filled with "📅 Bei einem Treffen".

Then further: when you choose that, you don't get the "selected" checkmark on the option as intended, because the selected logic is comparing to the untranslated text.

Those bugs are all fixed in the subsequent commit. But it'd be good to fix them all (including the latter one) in this separate commit. That can also simplify the subsequent commit that's meant to be about status emoji.

I'll try pushing a quick revision that does that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, right! Thanks.

@gnprice
Copy link
Member

gnprice commented Mar 8, 2022

OK, pushed that revision -- please take a look.

Meanwhile I'll go read those last five commits.

And do you want to mark the PR as non-draft? I believe it was draft only because of the emoji not being shown.

Copy link
Member

@gnprice gnprice left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And a few comments below on those new commits. Let's plan for those to go in a separate PR, as the first part is so close to merging.

Comment on lines 82 to 83
<View style={componentStyles.textAndEmojiWrapper}>
<View>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, what's the story with this additional layer of View-wrapper?

Investigating, I think you can drop the textAndEmojiWrapper view. It's just setting things that are already present in the parent's style. What is needed, besides adding the emoji at the end, is to drop the textWrapper styling and make this name-plus-email View have no style -- the flex: 1 is being unhelpful.

Copy link
Contributor Author

@chrisbobbe chrisbobbe Mar 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great! That's a nice simplification; thanks. (I tested it out on my iPhone and didn't see any problems.)

Copy link
Contributor Author

@chrisbobbe chrisbobbe Mar 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah: I must have forgotten to test 2ba88b6 (merged with this simplification) with an unread count.

Screenshot on my discarded revision (205fe61):

image

Screenshot on main:

image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW here's what it looks like with the fix, if both lines of text are visible:

image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines 356 to 359
.status-emoji {
height: 1.4rem;
margin-left: 2px;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This spacing seems a bit tight:
image

What does web do?

(That's with a Unicode emoji. This might look different for image emoji.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Web has a margin-left of 2px also:

image

Somehow 2px ends up looking less tight on web than on mobile; not sure why.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps web has a space between them in the HTML? I notice these templates (and the handy snapshots of what they produce) don't.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think maybe web doesn't have a space between them in the HTML? If I'm reading it correctly:

https://github.com/zulip/zulip/blob/3264361f6305082ba780882194bcd5cca83351a9/static/templates/message_body.hbs#L7

<span class="sender_name auto-select" role="button" tabindex="0">{{msg/sender_full_name}}{{> status_emoji msg/status_emoji_info}}</span>

And a screenshot from Chrome devtools:

image

Copy link
Contributor Author

@chrisbobbe chrisbobbe Mar 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if there were a space, it would show up in the devtools like "Chris Bobbe ". I notice that those quote marks in the devtools don't render in the web app.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I agree -- that appears to show there's no space in the HTML.

Comment on lines 52 to 53
export const getUserStatusFromModelState = (state: UserStatusesState, userId: UserId): UserStatus =>
state.get(userId, kUserStatusZero);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's put this next to the definition of the state type itself, where that fact about kUserStatusZero lives. I.e. let's put it in userStatusesCore.js.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh interesting. In its new location there, maybe it doesn't have to have this super long name? Maybe it can just be getUserStatus. (It couldn't be that if it stayed in userStatusesModel.js because that name would collide with the existing selector there.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would work well under a convention where we used these names relative to the modules they're in -- like import * as userStatusesCore from …. But our usual convention is instead to import the individual names directly. Under that convention, it's pretty helpful that we keep the individual names unique -- when the names are ambiguous, it gets confusing.

We've currently got

  statusText: string | null

so this new code gives the same results, but it's clearer.
…state

We've *said* statusText is `string | null` (changed from
`string | void` in our recent model rework, 2106381).

I would like to make it `string`, to match the `value` prop of the
`Input` component type. That seems like a responsible thing for
UserStatusScreen to do, since it's using the Input as a controlled
input: https://reactjs.org/docs/forms.html#controlled-components

Before we do that, we should at least fix this inconsistency, where
the value of the input corresponds to "unset" (empty string) but
statusText doesn't represent "unset" the way it says it will (null).

It hasn't been a live bug, and this is NFC, because it just so
happens that api.updateUserStatus converts to the empty string
anyway, to talk to the server.
UserStatusScreen has undertaken to use Input as a controlled input,
where at all times it's telling the Input what `value` it should
have. https://reactjs.org/docs/forms.html#controlled-components

It seems like the responsible thing for UserStatusScreen to do, for
clarity, is to at least store the value meant for `value` in a
proper state variable. We'll solidify that role with a rename,
coming next.
We can drop "status" because it's implied (this is the
UserStatusScreen). And it's good to be clear that we're working with
the current value of the input, which could be different from the
status text that's actually in effect, that the server knows about.
These are nice and simple, and we could probably do fine without
them. But it sets a good pattern for the emoji input, coming soon.
This seems helpful. It's easy to end up with leading or trailing
spaces in a text input, and want them to be treated as though they
weren't there. The server may even trim the text status too, I'm not
sure.

Note that we don't do this by fussing with the actual value of the
text input. That'd be easy to do, since we're using Input as a
controlled input -- but it would be a misuse of that control. In
particular, we'd introduce a bug like the following, on a topic
input, fixed in zulip#5098:

> Some bug prevented adding a space at the end of what I was typing
> specifically on the topic and maybe stream input (the space would
> appear and then immediately disappear, I think) ... so I was only
> able to create the topic "bog plants" by typing "bogplants" and
> then moving my cursor.
We don't like using NavigationService (zulip#4417), so this is nice to be
able to do.

Not *quite* NFC: if we somehow manage to have two consecutive
EmojiPickerScreens at the top of the stack, we'll now just pop one
of them instead of both, because we lose `navigateBack`'s special
logic with its `sameRoutesCount` value. But that logic is designed
for `ChatScreen` -- we do expect to have multiple of `ChatScreen`s
at the top of the stack sometimes. We don't expect that with
`EmojiPickerScreen`s.
…nput

For now, just leave the status-text input by itself, filling all of
the new View.

And move its margin to the new View; we'll want it to apply to the
emoji input too, coming soon.
Taking care not to send the status-emoji params to the server if the
server doesn't support status emoji.
This doesn't yet change the main behavior of the button when
pressed, i.e., we still clear the inputs *and* tell the server that
we want to unset the status.

Soon, we'll have it match the web app by only clearing the inputs.
Then the user can apply the change with the submit button, which
we'll rename from "Update" to "Save".
…icon

I think this is much more common wording.

And the checkmark icon is potentially confusing because it looks
just like the checkmark on one of the status suggestions
(SelectableOptionRow), if you've selected one. And it just doesn't
feel normal to have an icon at all for a simple "OK" button like
this.
This is another place where it's nice to use our
status <-> input-value conversion functions. The logic is not
complicated -- "Has the emoji or text been touched?" -- so it's nice
to see this code doesn't have ||, ??, and () ? () : () all over the
place.
When available, that is. Support was added in FL 86.

If emoji statuses are supported, we use the suggested emoji for the
emoji status, and the suggested text for the text status.

If emoji statuses aren't supported…we do end up just cutting the
emoji out of the status suggestion, which I guess is a small
downside. For these older servers, we could have kept up what we do
now: stick the status emoji at the start of the suggested text. But
I think achieving that has a higher complexity cost than it's worth.
I've never really used this UI, I'm not sure how common it is to do
so, and people are still free to depart from the suggested text,
including by adding emojis, by using the text input.
@chrisbobbe
Copy link
Contributor Author

Great, thanks for the review! Revision pushed, with those last 5 commits set aside for a separate PR.

@chrisbobbe chrisbobbe marked this pull request as ready for review March 9, 2022 02:33
@gnprice
Copy link
Member

gnprice commented Mar 9, 2022

Looks good! Merging.

One UI point that would be good to fix in the next PR, but I think isn't a blocker to merge: the status emoji end up looking faded, in both the selection UI and the profile screen. Like this:
image

Note the bright blue where the emoji appears in the list of suggestions, and the translucent-looking faded blue where it appears above. And similarly on the profile screen:
image

@gnprice gnprice merged commit 6d62335 into zulip:main Mar 9, 2022
@chrisbobbe chrisbobbe deleted the pr-set-emoji-status-ui branch March 9, 2022 03:11
@chrisbobbe
Copy link
Contributor Author

chrisbobbe commented Mar 9, 2022

Thanks for the reviews! Just sent #5283.

chrisbobbe added a commit to chrisbobbe/zulip-mobile that referenced this pull request Mar 15, 2022
See the code comments, and see screenshots of the bug at
  zulip#5277 (comment)
chrisbobbe added a commit to chrisbobbe/zulip-mobile that referenced this pull request Mar 16, 2022
See the code comments, and see screenshots of the bug at
  zulip#5277 (comment)

And Greg's suggestion to use this spacer View, to help keep the code
simpler:
  zulip#5297 (comment)
gnprice pushed a commit to chrisbobbe/zulip-mobile that referenced this pull request Mar 16, 2022
See the code comments, and see screenshots of the bug at
  zulip#5277 (comment)

And Greg's suggestion to use this spacer View, to help keep the code
simpler:
  zulip#5297 (comment)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add "Busy" status to emoji status picker
2 participants