From dea832fdfa1a7dd25a823acb5069e90c2c38038e Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Tue, 14 Feb 2023 14:53:10 -0800 Subject: [PATCH 1/2] LegalScreen: Turn into a global, all-accounts screen The user should be equally aware of the terms for all realms the app is interacting with, and that includes realms other than the active account's realm. (There would be even more interaction in a future with better multi-account support.) Rather than make the user traverse all realms with the "active account" pointer to find all the relevant terms, it seems nicer to show them together on this one page. --- src/nav/AppNavigator.js | 2 +- src/settings/LegalScreen.js | 124 ++++++++++++++++++++++++++++-------- 2 files changed, 98 insertions(+), 28 deletions(-) diff --git a/src/nav/AppNavigator.js b/src/nav/AppNavigator.js index 442e5f775b1..8238ab9e770 100644 --- a/src/nav/AppNavigator.js +++ b/src/nav/AppNavigator.js @@ -184,7 +184,6 @@ export default function AppNavigator(props: Props): Node { name="notif-troubleshooting" component={useHaveServerDataGate(NotifTroubleshootingScreen)} /> - @@ -205,6 +204,7 @@ export default function AppNavigator(props: Props): Node { } /> + ); diff --git a/src/settings/LegalScreen.js b/src/settings/LegalScreen.js index 8cd7ef98735..e3d0122cd42 100644 --- a/src/settings/LegalScreen.js +++ b/src/settings/LegalScreen.js @@ -3,24 +3,80 @@ import React, { useCallback } from 'react'; import type { Node } from 'react'; +import { createSelector } from 'reselect'; import type { RouteProp } from '../react-navigation'; import type { AppNavigationProp } from '../nav/AppNavigator'; -import { useGlobalSelector, useSelector } from '../react-redux'; +import { useGlobalSelector } from '../react-redux'; import Screen from '../common/Screen'; import NavRow from '../common/NavRow'; import ZulipText from '../common/ZulipText'; import { openLinkWithUserPreference } from '../utils/openLink'; -import { getRealmUrl, getRealmName, getGlobalSettings } from '../selectors'; +import { getRealmName, getGlobalSettings } from '../selectors'; +import { getAccounts } from '../directSelectors'; +import type { GlobalSelector } from '../reduxTypes'; +import { getAccount, tryGetActiveAccountState } from '../account/accountsSelectors'; +import { identityOfAccount, keyOfIdentity } from '../account/accountMisc'; +import { getHaveServerData } from '../haveServerDataSelectors'; + +/** + * Data for all realms represented in `state.accounts`, logged-in or not, + * unique by URL. + * + * The realm name will be missing when we don't have server data for any + * account on the realm. + */ +type ViewModel = $ReadOnlyArray<{| + +realm: URL, + +name: string | null, + +policiesUrl: URL, +|}>; + +const getViewModel: GlobalSelector = createSelector( + getAccounts, + tryGetActiveAccountState, + (accounts, activeAccountState) => { + const result = new Map(accounts.map(a => [a.realm.toString(), null])); + + accounts.forEach(account => { + const realmStr = account.realm.toString(); + + if (result.get(realmStr) != null) { + return; + } + + // TODO(#5006): Add realm name for any account we have server data for, + // not just the active account. + if ( + activeAccountState + && keyOfIdentity(identityOfAccount(getAccount(activeAccountState))) + === keyOfIdentity(identityOfAccount(account)) + && getHaveServerData(activeAccountState) + ) { + result.set(realmStr, getRealmName(activeAccountState)); + } + }); + + return [...result.entries()].map(([realmStr, name]) => { + const realm = new URL(realmStr); + return { + realm, + name, + policiesUrl: new URL('/policies/?nav=no', realm), + }; + }); + }, +); type Props = $ReadOnly<{| navigation: AppNavigationProp<'legal'>, route: RouteProp<'legal', void>, |}>; -/** (NB this is a per-account screen: it leads to this realm's policies.) */ +/** + * A global, all-accounts screen linking to terms for all realms we know about. + */ export default function LegalScreen(props: Props): Node { - const realm = useSelector(getRealmUrl); - const realmName = useSelector(getRealmName); + const viewModel = useGlobalSelector(getViewModel); const globalSettings = useGlobalSelector(getGlobalSettings); @@ -28,31 +84,45 @@ export default function LegalScreen(props: Props): Node { openLinkWithUserPreference(new URL('https://zulip.com/policies/?nav=no'), globalSettings); }, [globalSettings]); - const openRealmPolicies = useCallback(() => { - openLinkWithUserPreference(new URL('/policies/?nav=no', realm), globalSettings); - }, [realm, globalSettings]); - return ( - }, - }} - onPress={openRealmPolicies} - type="external" - /> + {viewModel.map(({ realm, name, policiesUrl }) => ( + + ), + }, + }} + subtitle={ + // It's nice to be explicit about where the policies live, + // though the "?nav=no" is a bit annoying. But also, this line + // disambiguates multiple realms with the same name; the name is + // shown (when we have it) in `title`. + { text: '{_}', values: { _: policiesUrl.toString() } } + } + onPress={() => { + openLinkWithUserPreference(policiesUrl, globalSettings); + }} + type="external" + /> + ))} ); } From a5123e9b17f6b56cede958111a677c80e1c83d59 Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Fri, 10 Mar 2023 18:56:09 -0800 Subject: [PATCH 2/2] LegalScreen: Put policies URL on "Zulip terms" line Since the realms' individual policy URLs are shown (really just to disambiguate different realms with the same name) it looks a bit odd if the "Zulip terms" line doesn't have a URL, so add it. --- src/settings/LegalScreen.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/settings/LegalScreen.js b/src/settings/LegalScreen.js index e3d0122cd42..0269bc58f07 100644 --- a/src/settings/LegalScreen.js +++ b/src/settings/LegalScreen.js @@ -72,6 +72,8 @@ type Props = $ReadOnly<{| route: RouteProp<'legal', void>, |}>; +const zulipPoliciesUrl = new URL('https://zulip.com/policies/?nav=no'); + /** * A global, all-accounts screen linking to terms for all realms we know about. */ @@ -81,12 +83,17 @@ export default function LegalScreen(props: Props): Node { const globalSettings = useGlobalSelector(getGlobalSettings); const openZulipPolicies = useCallback(() => { - openLinkWithUserPreference(new URL('https://zulip.com/policies/?nav=no'), globalSettings); + openLinkWithUserPreference(zulipPoliciesUrl, globalSettings); }, [globalSettings]); return ( - + {viewModel.map(({ realm, name, policiesUrl }) => (