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

StreamSettingsScreen: s/Private/Privacy/; use radio buttons, not a switch #5369

Merged
merged 6 commits into from
May 13, 2022

Conversation

chrisbobbe
Copy link
Contributor

@chrisbobbe chrisbobbe commented Apr 28, 2022

This includes my current revision for #5360, which we hope to merge soon. Done!


For #5250, we'll need to let the user choose one among more than two
options. That'll call for a radio-button-style input, rather than a
switch.

So, make a new component InputRowRadioButtons, and use it for an
input labeled "Privacy", replacing the SwitchRow input labeled
"Private".

And, to support that component, write and wire up a new
SelectableOptionsScreen.

@chrisbobbe chrisbobbe requested a review from gnprice April 28, 2022 20:13
@chrisbobbe
Copy link
Contributor Author

Before/after:

Apr-28-2022.13-16-55.mp4
Apr-28-2022.13-15-25.mp4

@alya
Copy link
Collaborator

alya commented May 4, 2022

Cool, looks good!

When we are adding all the privacy options, I think we should try including more detailed description text to each option, like we do in the web app.

@chrisbobbe
Copy link
Contributor Author

chrisbobbe commented May 4, 2022

I think we should try including more detailed description text to each option, like we do in the web app.

Yeah, I think so too. #5360 is meant to help the appearance when we have explanatory text that's multiple lines long, which is a case we haven't had before with that UI component.

@chrisbobbe
Copy link
Contributor Author

Thanks for the review, Alya! I've just rebased, and I'll mark this as non-draft for when Greg gets back from vacation.

@chrisbobbe chrisbobbe marked this pull request as ready for review May 4, 2022 20:23
@chrisbobbe
Copy link
Contributor Author

chrisbobbe commented May 10, 2022

Rebased. #5360 has been merged, making this branch just one commit. 🙂 (Though I acknowledge it's kind of a big diff.)

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! This code looks great -- the design makes sense, and the code is generally quite clean. A few comments below.

Comment on lines 16 to 17
title: string,
subtitle?: string,
Copy link
Member

Choose a reason for hiding this comment

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

These two should be strings waiting to be translated, right? I.e. not strings we're going to show literally to the user without passing through translation.

If so, it'd be good to make that clear in the code (and if not, to make the opposite clear). In particular, it's good for it to be clear where in the code the responsibility lies for invoking translation, so that we don't end up either failing to translate or attempting to double-translate.

I think a pretty good immediate way to do that would be to make the type LocalizableText. That's at least as good as any comment, plus it will prevent trying to use the string directly without translating it.

I don't think we currently have a way to ensure that a string not meant for translation -- e.g. one that's already been through translation -- accidentally gets translated. Now that I think about it, it'd probably be straightforward to do so by making LocalizableText an opaque type. But that's a project for another day.

Comment on lines 28 to 32
/** What the setting is about, e.g., "Theme". */
label: string,

/** What the setting is about, in a short sentence or two. */
description?: string,
Copy link
Member

Choose a reason for hiding this comment

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

Similar comment as for the title and subtitle above.

const { navigation, label, description, valueKey, items, onValueChange } = props;

const screenKey = useRef(
`selectable-options-${Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(36)}`,
Copy link
Member

Choose a reason for hiding this comment

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

nit: Probably this should be a helper function with a name, for the sake of both brevity and clarity. Which basically means take the existing eg.randString and put it somewhere in the non-test code.

Comment on lines 90 to 95
// TODO: Can use .push instead of .navigate? See if Flow types are wrong
// in saying we can't pass `key` when using .push.
navigation.navigate({
Copy link
Member

Choose a reason for hiding this comment

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

Hmm, curious.

Even if push won't accept a key, the behavior is fine, fortunately. I don't recall whether navigate's funky rewind-history behavior (in stack navigators) takes account of key to distinguish the desired new destination from existing routes on the stack -- but even if it doesn't, SelectableOptionsScreen is not going to push anything else on the stack. That means it'll never be somewhere earlier in the stack when we're here on whatever screen this InputRowRadioButtons appears on; so that funky rewinding won't ever trigger here; and the behavior of navigate will be exactly the same as that of push would have been.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Confirmed empirically that .push fails when we pass an object with a key, in the way that .navigate would handle:

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So, I'll make a note that using .navigate is fine.

Comment on lines +142 to +149
<Touchable onPress={handleRowPressed}>
<View style={styles.wrapper}>
Copy link
Member

Choose a reason for hiding this comment

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

Touchable itself already creates a wrapper View. (This smooths over some variation between the underlying primitives RN gives us on iOS vs. Android; see the Touchable implementation.)

Can we use that one instead of having a second wrapper inside it? That'd look like:

Suggested change
<Touchable onPress={handleRowPressed}>
<View style={styles.wrapper}>
<Touchable onPress={handleRowPressed} style={styles.wrapper}>

(and drop a </View> at the end, and reindent.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Then the <Touchable /> ends up with two children, which is apparently bad:

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 see. I guess we even have that in our jsdoc for Touchable:

 * @prop [children] - A single component (not zero, or more than one.)

So be it, then.

Comment on lines 99 to 105
// Live-update the selectable-options screen.
useEffect(() => {
navigation.dispatch(
state =>
state.routes.find(route => route.key === screenKey)
? { ...CommonActions.setParams(screenParams), source: screenKey }
: CommonActions.reset(state), // no-op
Copy link
Member

Choose a reason for hiding this comment

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

Huh, funky. I'm reading about the relevant part of the React Nav API here:
https://reactnavigation.org/docs/5.x/navigation-prop#dispatch
https://reactnavigation.org/docs/5.x/navigation-actions/#setparams

Do we need the conditional and the no-op case? What happens if we just say:

Suggested change
// Live-update the selectable-options screen.
useEffect(() => {
navigation.dispatch(
state =>
state.routes.find(route => route.key === screenKey)
? { ...CommonActions.setParams(screenParams), source: screenKey }
: CommonActions.reset(state), // no-op
// Live-update the selectable-options screen.
useEffect(() => {
navigation.dispatch(
{ ...CommonActions.setParams(screenParams), source: screenKey },

; does that blow up if the route key passed as source isn't actually found on any route?

Copy link
Contributor Author

@chrisbobbe chrisbobbe May 10, 2022

Choose a reason for hiding this comment

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

Yeah, good thought—whatever we end up with, I should probably add a code comment. On pressing the "Edit stream" button, which brings me to EditStreamScreen, I get this:

It's dismissable and probably harmless? Glad to have the dev-only warning, I guess, rather than a silent failure, to help debug when one does expect navigation.dispatch to do something visible and instead nothing happens. I could do a LogBox suppression so that we don't even see this particular warning in dev.

We can set onUnhandledAction on the navigation container, to set the behavior: https://reactnavigation.org/docs/navigation-container#onunhandledaction . That doc is for 6.x. The doc for 5.x, which we're on, doesn't mention the prop, but I'm seeing its implementation in node_modules. By default, it looks like nothing happens in production.

Copy link
Member

Choose a reason for hiding this comment

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

Cool. I think given that it's an error in dev, and it's not hard to write the code to avoid it, it's best to avoid it.

Comment on lines +35 to +56
// This param is a function, so React Nav is right to point out that
// it isn't serializable. But this is fine as long as we don't try to
// persist navigation state for this screen or set up deep linking to
// it, hence the LogBox suppression below.
//
// React Navigation doesn't offer a more sensible way to have us pass
// the selection to the calling screen. …We could store the selection
// as a route param on the calling screen, or in Redux. But from this
// screen's perspective, that's basically just setting a global
// variable. Better to offer this explicit, side-effect-free way for
// the data to flow where it should, when it should.
onRequestSelectionChange: (itemKey: TItemKey, requestedValue: boolean) => void,
|},
>,
|}>;

// React Navigation would give us a console warning about non-serializable
// route params. For more about the warning, see
// https://reactnavigation.org/docs/5.x/troubleshooting/#i-get-the-warning-non-serializable-values-were-found-in-the-navigation-state
// See comment on this param, above.
LogBox.ignoreLogs([/selectable-options > params\.onRequestSelectionChange \(Function\)/]);
Copy link
Member

Choose a reason for hiding this comment

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

This all makes sense! Thanks for the explanation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's basically copied from EmojiPickerScreen. 😛

Copy link
Member

Choose a reason for hiding this comment

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

Ha, OK -- I thought it sounded a bit familiar, but figured I must have looked at a version of this PR earlier.

Still, that's also text that you wrote, so the thanks aren't wrong 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The reasoning was yours, though, as I remember! We can share the thanks, then? 😅

@chrisbobbe
Copy link
Contributor Author

Thanks for the review! Revision pushed.

Comment on lines +55 to +43
{ key: 'public', title: 'Public' },
{ key: 'private', title: 'Private' },
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 was wrong in the office today saying 'Public' and 'Private' aren't in messages_en.json; they are. 🙂

@chrisbobbe
Copy link
Contributor Author

Another revision pushed, with some things we discussed in the office yesterday.

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! All looks good except one bug in the SelectableOptionRow translation refactor.

Comment on lines 33 to 28
getFilteredLanguageList: string => Language[] = filter => {
const list = this.getTranslatedLanguages();

getFilteredLanguageList: string => $ReadOnlyArray<Language> = filter => {
if (!filter) {
return list;
return languages;
}

return list.filter(item => {
return languages.filter(item => {
Copy link
Member

Choose a reason for hiding this comment

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

This change doesn't seem right -- it means that the search will look at the English name and selfname, instead of the current UI language's name and selfname.

The code around here could be improved in general, but probably the best thing for the current task is to keep the change to this code simple and local: just handle subtitle= below in the same way as title=, without changing these other functions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indeed! Thanks for catching this.

…itch

For zulip#5250, we'll need to let the user choose one among more than two
options. That'll call for a radio-button-style input, rather than a
switch.

So, make a new component InputRowRadioButtons, and use it for an
input labeled "Privacy", replacing the SwitchRow input labeled
"Private".

And, to support that component, write and wire up another new
component, SelectableOptionsScreen.
@chrisbobbe
Copy link
Contributor Author

Thanks for the review! Revision pushed.

@gnprice
Copy link
Member

gnprice commented May 13, 2022

Thanks! Looks good; merging.

@gnprice gnprice merged commit e720924 into zulip:main May 13, 2022
@chrisbobbe chrisbobbe deleted the pr-stream-settings-fixes branch May 14, 2022 00:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants