Skip to content

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

Merged
gnprice merged 6 commits intozulip:mainfrom
chrisbobbe:pr-stream-settings-fixes
May 13, 2022
Merged

StreamSettingsScreen: s/Private/Privacy/; use radio buttons, not a switch#5369
gnprice merged 6 commits intozulip:mainfrom
chrisbobbe:pr-stream-settings-fixes

Conversation

@chrisbobbe
Copy link
Copy Markdown
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
Copy Markdown
Contributor Author

Before/after:

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

@alya
Copy link
Copy Markdown
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
Copy Markdown
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 chrisbobbe force-pushed the pr-stream-settings-fixes branch from fc9cba4 to aef1f02 Compare May 4, 2022 20:22
@chrisbobbe
Copy link
Copy Markdown
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 chrisbobbe force-pushed the pr-stream-settings-fixes branch from aef1f02 to 9b9b90d Compare May 10, 2022 04:25
@chrisbobbe
Copy link
Copy Markdown
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
Copy Markdown
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 thread src/common/InputRowRadioButtons.js Outdated
Comment on lines +16 to +17
title: string,
subtitle?: string,
Copy link
Copy Markdown
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 thread src/common/InputRowRadioButtons.js Outdated
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
Copy Markdown
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.

Comment thread src/common/InputRowRadioButtons.js Outdated
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
Copy Markdown
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 thread src/common/InputRowRadioButtons.js Outdated
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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 thread src/common/InputRowRadioButtons.js Outdated
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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 chrisbobbe force-pushed the pr-stream-settings-fixes branch from 9b9b90d to 5ceec85 Compare May 10, 2022 22:36
@chrisbobbe
Copy link
Copy Markdown
Contributor Author

Thanks for the review! Revision pushed.

Comment on lines +55 to +43
{ key: 'public', title: 'Public' },
{ key: 'private', title: 'Private' },
Copy link
Copy Markdown
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 chrisbobbe force-pushed the pr-stream-settings-fixes branch from 5ceec85 to f39ba15 Compare May 11, 2022 21:54
@chrisbobbe
Copy link
Copy Markdown
Contributor Author

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

Copy link
Copy Markdown
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 thread src/settings/LanguagePicker.js Outdated
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
Copy Markdown
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
Copy Markdown
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 chrisbobbe force-pushed the pr-stream-settings-fixes branch from f39ba15 to e720924 Compare May 13, 2022 00:45
@chrisbobbe
Copy link
Copy Markdown
Contributor Author

Thanks for the review! Revision pushed.

@gnprice
Copy link
Copy Markdown
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