-
-
Notifications
You must be signed in to change notification settings - Fork 654
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
Conversation
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 @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.
src/user-statuses/EmojiInput.js
Outdated
import type { EmojiType } from '../types'; | ||
import { createStyleSheet, BORDER_COLOR } from '../styles'; | ||
|
||
export type Value = null | {| +code: string, +name: string, +type: EmojiType |}; |
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: type before code, because the code lives in a namespace determined by the type
(and then same thing where constructing these in UserStatusScreen.js
)
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); |
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 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.)
...(Platform.OS === 'ios' | ||
? { | ||
borderWidth: 1, | ||
borderColor: BORDER_COLOR, | ||
borderRadius: 2, | ||
padding: 8, | ||
} | ||
: Object.freeze({})), |
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 interesting. Why are the border and padding only on iOS?
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.
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,
},
}),
},
});
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.
Ah, got it. IIRC that conditional is there in order to conform to platform conventions. This UI might call for something else, though.
button: { | ||
flex: 1, |
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.
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.
static/translations/messages_en.json
Outdated
@@ -241,7 +241,6 @@ | |||
"Share failed": "Share failed", | |||
"Download complete": "Download complete", | |||
"Download file": "Download file", | |||
"Update": "Update", |
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.
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'}
// This is a separate file due to Prettier having issues with formatting unicode | ||
// Using `prettier-ignore` does not help. |
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, seems like this comment stops applying.
If so, we can also fold this file into UserStatusScreen.js
.
title={ | ||
zulipFeatureLevel >= 86 | ||
? `${parseUnicodeEmojiCode(emoji.emoji_code)} ${translatedText}` | ||
: text |
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, 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 |
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.
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.
setTextInputValue(translatedText); | ||
setEmojiInputValue(inputValueFromStatusEmoji(emoji)); |
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.
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'], |
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 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.
94ab441
to
5aa031f
Compare
Thanks for the review! Revision pushed.
Makes sense. I put a commit to show the emoji status on that profile screen you're talking about ( 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. |
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! 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.
const { navigation } = props; | ||
const _ = useContext(TranslationContext); |
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.
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.
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.
Ah, indeed! I've gone and put it with the selectors for the next revision.
<SelectableOptionRow | ||
key={item} | ||
itemKey={item} | ||
title={item} | ||
title={_(item)} | ||
selected={item === statusTextFromInputValue(textInputValue)} | ||
onRequestSelectionChange={itemKey => { | ||
setTextInputValue(_(itemKey)); |
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.
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.
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.
Ah, right! Thanks.
5aa031f
to
42310c9
Compare
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. |
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.
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.
src/users/UserItem.js
Outdated
<View style={componentStyles.textAndEmojiWrapper}> | ||
<View> |
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, 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.
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.
Great! That's a nice simplification; thanks. (I tested it out on my iPhone and didn't see any problems.)
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 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 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.
src/webview/static/base.css
Outdated
.status-emoji { | ||
height: 1.4rem; | ||
margin-left: 2px; | ||
} |
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 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 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.
Perhaps web has a space between them in the HTML? I notice these templates (and the handy snapshots of what they produce) don't.
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 maybe web doesn't have a space between them in the HTML? If I'm reading it correctly:
<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:
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 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.
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, I agree -- that appears to show there's no space in the HTML.
export const getUserStatusFromModelState = (state: UserStatusesState, userId: UserId): UserStatus => | ||
state.get(userId, kUserStatusZero); |
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 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
.
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.
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.)
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.
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.
42310c9
to
6d62335
Compare
Great, thanks for the review! Revision pushed, with those last 5 commits set aside for a separate PR. |
Thanks for the reviews! Just sent #5283. |
See the code comments, and see screenshots of the bug at zulip#5277 (comment)
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)
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)
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