From 199a19069ed570920bc9c98a0fd632306537aaa2 Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Fri, 14 May 2021 12:28:08 -0400 Subject: [PATCH 01/12] RawLabel [nfc]: Support nesting, for style inheritance. One bit of `Text`'s API that `RawLabel` hasn't been exposing is its support for creating an element with `children` and having those children inherit the element's styles [1]: > For React Native, we decided to use web paradigm for [displaying > formatted text] where you can nest text to achieve the same > effect. (Before this commit, `RawLabel` was silently accepting `children` and ignoring it.) `RawLabel`, unlike `Label`, has the flexibility to match `Text`'s support for passing some React nodes besides strings. That's because `RawLabel` is not responsible for passing a string to a function to get it translated. While we might have just removed the `text` prop and had all callers exclusively use `children`, this seems more disruptive than is worthwhile. Do enforce that exactly one of `children` and `text` is passed, though. [1] https://reactnative.dev/docs/text#nested-text --- src/common/RawLabel.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/common/RawLabel.js b/src/common/RawLabel.js index ad4df15280c..3918640f42b 100644 --- a/src/common/RawLabel.js +++ b/src/common/RawLabel.js @@ -1,4 +1,5 @@ /* @flow strict-local */ +import invariant from 'invariant'; import React, { PureComponent } from 'react'; import { Text } from 'react-native'; @@ -7,14 +8,15 @@ import { ThemeContext } from '../styles'; type Props = $ReadOnly<{| ...$Exact>, - text: string, + text?: string, |}>; /** - * A component that on top of a standard Text component - * ensures consistent styling for the default and night themes. + * A thin wrapper for `Text` that ensures a consistent, themed style. * - * Unlike `Label` it does not translate its contents. + * Unlike `Label`, it does not translate its contents. + * + * Pass either `text` or `children`, but not both. * * @prop text - Contents for Text. * @prop [style] - Can override our default style for this component. @@ -32,11 +34,14 @@ export default class RawLabel extends PureComponent { }; render() { - const { text, style, ...restProps } = this.props; + const { text, children, style, ...restProps } = this.props; + + invariant(!!text !== !!children, 'pass either `text` or `children`'); return ( {text} + {children} ); } From 56819636a7cb242d4d9354bda246c5ab8d7c9fd9 Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Fri, 14 May 2021 13:51:21 -0400 Subject: [PATCH 02/12] Label [nfc]: Make `Label` explicitly a wrapper for `RawLabel`. This way, the components don't both have to have code for default styling. --- src/common/Label.js | 34 ++++++++++------------------------ 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/src/common/Label.js b/src/common/Label.js index 3ab20f7410f..5444f505003 100644 --- a/src/common/Label.js +++ b/src/common/Label.js @@ -1,46 +1,32 @@ /* @flow strict-local */ import React, { PureComponent } from 'react'; -import { Text } from 'react-native'; import TranslatedText from './TranslatedText'; -import type { ThemeData } from '../styles'; -import { ThemeContext } from '../styles'; +import type { BoundedDiff } from '../generics'; +import RawLabel from './RawLabel'; import type { LocalizableText } from '../types'; type Props = $ReadOnly<{| - ...$Exact>, + ...BoundedDiff<$Exact>, {| children: ?React$Node |}>, text: LocalizableText, |}>; /** - * A component that on top of a standard Text component - * provides seamless translation and ensures consistent - * styling for the default and night themes. + * A wrapper for `RawLabel` that also translates the text. * - * Use `RawLabel` if you don't want the text translated. + * Use `RawLabel` instead if you don't want the text translated. * - * @prop text - Translated before putting inside Text. - * @prop [style] - Can override our default style for this component. - * @prop ...all other Text props - Passed through verbatim to Text. - * See upstream: https://reactnative.dev/docs/text + * Unlike `RawLabel`, only accepts a `LocalizableText`, as the `text` + * prop, and doesn't support `children`. */ export default class Label extends PureComponent { - static contextType = ThemeContext; - context: ThemeData; - - styles = { - label: { - fontSize: 15, - }, - }; - render() { - const { text, style, ...restProps } = this.props; + const { text, ...restProps } = this.props; return ( - + - + ); } } From 15105fb56464172722ff812053901953965ebc8b Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Fri, 14 May 2021 15:06:37 -0400 Subject: [PATCH 03/12] RawLabel [nfc]: Bring together default styles; comment on a gotcha. --- src/common/RawLabel.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/common/RawLabel.js b/src/common/RawLabel.js index 3918640f42b..6dc71c209ad 100644 --- a/src/common/RawLabel.js +++ b/src/common/RawLabel.js @@ -27,19 +27,21 @@ export default class RawLabel extends PureComponent { static contextType = ThemeContext; context: ThemeData; - styles = { - label: { - fontSize: 15, - }, - }; - render() { const { text, children, style, ...restProps } = this.props; invariant(!!text !== !!children, 'pass either `text` or `children`'); + // These attributes will be applied unless specifically overridden + // with the `style` prop -- even if this `` is nested + // and would otherwise inherit the attributes from its ancestors. + const aggressiveDefaultStyle = { + fontSize: 15, + color: this.context.color, + }; + return ( - + {text} {children} From cb40334368d4187d1e7e89a8a6cc58e3cecd12b6 Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Fri, 14 May 2021 13:58:29 -0400 Subject: [PATCH 04/12] WebLink [nfc]: Move out some styling code that callers should handle. Using `Text`'s nesting interface we've had `RawLabel` start exposing. --- src/common/WebLink.js | 3 --- src/start/PasswordAuthScreen.js | 10 +++++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/common/WebLink.js b/src/common/WebLink.js index f1e0589f2d6..d0fc359bd1c 100644 --- a/src/common/WebLink.js +++ b/src/common/WebLink.js @@ -13,10 +13,7 @@ type Props = $ReadOnly<{| const componentStyles = createStyleSheet({ link: { - marginTop: 10, - fontSize: 15, color: BRAND_COLOR, - textAlign: 'right', }, }); diff --git a/src/start/PasswordAuthScreen.js b/src/start/PasswordAuthScreen.js index ecd4d2f2b2c..4b1417a49d6 100644 --- a/src/start/PasswordAuthScreen.js +++ b/src/start/PasswordAuthScreen.js @@ -16,14 +16,20 @@ import { WebLink, ZulipButton, ViewPlaceholder, + RawLabel, } from '../common'; import { isValidEmailFormat } from '../utils/misc'; import { loginSuccess } from '../actions'; const styles = createStyleSheet({ linksTouchable: { + marginTop: 10, alignItems: 'flex-end', }, + forgotPasswordText: { + fontSize: 15, + textAlign: 'right', + }, }); type Props = $ReadOnly<{| @@ -128,7 +134,9 @@ class PasswordAuthScreen extends PureComponent { /> - + + + ); From f3b61434557a0cc99276d6395238d5f986ab8ef7 Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Fri, 14 May 2021 13:59:49 -0400 Subject: [PATCH 05/12] PasswordAuthScreen [nfc]: Don't redundantly set a font size to its default. The default is provided by `RawLabel`. --- src/start/PasswordAuthScreen.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/start/PasswordAuthScreen.js b/src/start/PasswordAuthScreen.js index 4b1417a49d6..c51b6762d94 100644 --- a/src/start/PasswordAuthScreen.js +++ b/src/start/PasswordAuthScreen.js @@ -27,7 +27,6 @@ const styles = createStyleSheet({ alignItems: 'flex-end', }, forgotPasswordText: { - fontSize: 15, textAlign: 'right', }, }); From 9559a75d1eb5121c177abb6abf344f6b94e38335 Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Thu, 20 May 2021 21:05:22 -0400 Subject: [PATCH 06/12] sessionReducer [nfc]: Comment on the mechanism for not persisting to disk. --- src/session/sessionReducer.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/session/sessionReducer.js b/src/session/sessionReducer.js index 2fafcde94bf..270343884c1 100644 --- a/src/session/sessionReducer.js +++ b/src/session/sessionReducer.js @@ -19,6 +19,11 @@ import { hasAuth } from '../account/accountsSelectors'; /** * Miscellaneous non-persistent state about this run of the app. + * + * These state items are stored in `session.state`, and 'session' is + * in `discardKeys` in src/boot/store.js. That means these values + * won't be persisted between sessions; on startup, they'll all be + * initialized to their default values. */ export type SessionState = {| eventQueueId: number, From aab915f287e1dce92c09882615f95155096a2e27 Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Fri, 14 May 2021 14:46:59 -0400 Subject: [PATCH 07/12] session: Add Redux boilerplate for dismissing the Zulip version nag banner. Which we'll add in the next commit, using this setup. --- src/actionConstants.js | 2 ++ src/actionTypes.js | 6 ++++++ src/session/__tests__/sessionReducer-test.js | 9 +++++++++ src/session/sessionActions.js | 12 +++++++++++- src/session/sessionReducer.js | 20 ++++++++++++++++++++ 5 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/actionConstants.js b/src/actionConstants.js index 396fb65141e..b8bee6433ae 100644 --- a/src/actionConstants.js +++ b/src/actionConstants.js @@ -56,6 +56,8 @@ export const PRESENCE_RESPONSE: 'PRESENCE_RESPONSE' = 'PRESENCE_RESPONSE'; export const GET_USER_RESPONSE: 'GET_USER_RESPONSE' = 'GET_USER_RESPONSE'; export const SETTINGS_CHANGE: 'SETTINGS_CHANGE' = 'SETTINGS_CHANGE'; export const DEBUG_FLAG_TOGGLE: 'DEBUG_FLAG_TOGGLE' = 'DEBUG_FLAG_TOGGLE'; +export const DISMISS_SERVER_COMPAT_NOTICE: 'DISMISS_SERVER_COMPAT_NOTICE' = + 'DISMISS_SERVER_COMPAT_NOTICE'; export const GOT_PUSH_TOKEN: 'GOT_PUSH_TOKEN' = 'GOT_PUSH_TOKEN'; export const ACK_PUSH_TOKEN: 'ACK_PUSH_TOKEN' = 'ACK_PUSH_TOKEN'; diff --git a/src/actionTypes.js b/src/actionTypes.js index 062048fd5d1..640c7fa0d97 100644 --- a/src/actionTypes.js +++ b/src/actionTypes.js @@ -55,6 +55,7 @@ import { EVENT_SUBMESSAGE, EVENT_SUBSCRIPTION, EVENT, + DISMISS_SERVER_COMPAT_NOTICE, } from './actionConstants'; import type { @@ -132,6 +133,10 @@ type DebugFlagToggleAction = {| value: boolean, |}; +type DismissServerCompatNoticeAction = {| + type: typeof DISMISS_SERVER_COMPAT_NOTICE, +|}; + type AccountSwitchAction = {| type: typeof ACCOUNT_SWITCH, index: number, @@ -610,6 +615,7 @@ type SessionAction = | AppOrientationAction | GotPushTokenAction | DebugFlagToggleAction + | DismissServerCompatNoticeAction | ToggleOutboxSendingAction; /** Covers all actions we ever `dispatch`. */ diff --git a/src/session/__tests__/sessionReducer-test.js b/src/session/__tests__/sessionReducer-test.js index e48dd9ca040..873164ec903 100644 --- a/src/session/__tests__/sessionReducer-test.js +++ b/src/session/__tests__/sessionReducer-test.js @@ -10,6 +10,7 @@ import { GOT_PUSH_TOKEN, TOGGLE_OUTBOX_SENDING, DEBUG_FLAG_TOGGLE, + DISMISS_SERVER_COMPAT_NOTICE, INITIAL_FETCH_START, } from '../../actionConstants'; import sessionReducer from '../sessionReducer'; @@ -112,4 +113,12 @@ describe('sessionReducer', () => { debug: { doNotMarkMessagesAsRead: false, someKey: true }, }); }); + + test('DISMISS_SERVER_COMPAT_NOTICE', () => { + const action = deepFreeze({ type: DISMISS_SERVER_COMPAT_NOTICE }); + expect(sessionReducer(baseState, action)).toEqual({ + ...baseState, + hasDismissedServerCompatNotice: true, + }); + }); }); diff --git a/src/session/sessionActions.js b/src/session/sessionActions.js index 5cefaf321ea..e1b38128251 100644 --- a/src/session/sessionActions.js +++ b/src/session/sessionActions.js @@ -1,6 +1,12 @@ /* @flow strict-local */ import type { Action, Orientation } from '../types'; -import { APP_ONLINE, APP_ORIENTATION, DEAD_QUEUE, DEBUG_FLAG_TOGGLE } from '../actionConstants'; +import { + APP_ONLINE, + APP_ORIENTATION, + DEAD_QUEUE, + DEBUG_FLAG_TOGGLE, + DISMISS_SERVER_COMPAT_NOTICE, +} from '../actionConstants'; export const appOnline = (isOnline: boolean): Action => ({ type: APP_ONLINE, @@ -21,3 +27,7 @@ export const debugFlagToggle = (key: string, value: boolean): Action => ({ key, value, }); + +export const dismissCompatNotice = (): Action => ({ + type: DISMISS_SERVER_COMPAT_NOTICE, +}); diff --git a/src/session/sessionReducer.js b/src/session/sessionReducer.js index 270343884c1..f49f990ee16 100644 --- a/src/session/sessionReducer.js +++ b/src/session/sessionReducer.js @@ -14,6 +14,7 @@ import { DEBUG_FLAG_TOGGLE, GOT_PUSH_TOKEN, LOGOUT, + DISMISS_SERVER_COMPAT_NOTICE, } from '../actionConstants'; import { hasAuth } from '../account/accountsSelectors'; @@ -61,6 +62,18 @@ export type SessionState = {| pushToken: string | null, debug: Debug, + + /** + * Whether `ServerCompatNotice` (which we'll add soon) has been + * dismissed this session. + * + * We put this in the per-session state deliberately, so that users + * see the notice on every startup until the server is upgraded. + * That's a better experience than not being able to load the realm + * on mobile at all, which is what will happen soon if the user + * doesn't act on the notice. + */ + hasDismissedServerCompatNotice: boolean, |}; const initialState: SessionState = { @@ -75,6 +88,7 @@ const initialState: SessionState = { debug: { doNotMarkMessagesAsRead: false, }, + hasDismissedServerCompatNotice: false, }; const rehydrate = (state, action) => { @@ -173,6 +187,12 @@ export default (state: SessionState = initialState, action: Action): SessionStat }, }; + case DISMISS_SERVER_COMPAT_NOTICE: + return { + ...state, + hasDismissedServerCompatNotice: true, + }; + default: return state; } From c8a88127e6bfe18dc29c61a9da20813c5423e34b Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Wed, 19 May 2021 18:20:06 -0400 Subject: [PATCH 08/12] ZulipTextButton: Add, to implement Material's "text button". --- src/common/ZulipTextButton.js | 113 ++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 src/common/ZulipTextButton.js diff --git a/src/common/ZulipTextButton.js b/src/common/ZulipTextButton.js new file mode 100644 index 00000000000..7174c96e773 --- /dev/null +++ b/src/common/ZulipTextButton.js @@ -0,0 +1,113 @@ +/* @flow strict-local */ +import React, { useMemo } from 'react'; +import { View } from 'react-native'; + +import type { LocalizableText } from '../types'; +import { BRAND_COLOR, createStyleSheet } from '../styles'; +import { Label } from '.'; +import Touchable from './Touchable'; + +// When adding a variant, take care that it's legitimate, it addresses a +// common use case consistently, and its styles are defined coherently. We +// don't want this component to grow brittle with lots of little flags to +// micromanage its styles. +type Variant = 'standard'; + +const styleSheetForVariant = (variant: Variant) => + createStyleSheet({ + // See https://material.io/components/buttons#specs. + touchable: { + height: 36, + paddingHorizontal: 8, + minWidth: 64, + }, + + // Chosen because of the value for this distance at + // https://material.io/components/banners#specs. A banner is one context + // where we've wanted to use text buttons. + leftMargin: { + marginLeft: 8, + }, + rightMargin: { + marginRight: 8, + }, + + // `Touchable` only accepts one child, so make sure it fills the + // `Touchable`'s full area and centers everything in it. + childOfTouchable: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, + + text: { + // From the spec: + // > Text labels need to be distinct from other elements. If the text + // > label isn’t fully capitalized, it should use a different color, + // > style, or layout from other text. + textTransform: 'uppercase', + color: BRAND_COLOR, + + textAlign: 'center', + textAlignVertical: 'center', + }, + }); + +type Props = $ReadOnly<{| + /** See note on the `Variant` type. */ + variant?: Variant, + + /** + * Give a left margin of the correct in-between space for a set of buttons + */ + leftMargin?: true, + + /** + * Give a right margin of the correct in-between space for a set of + * buttons + */ + rightMargin?: true, + + /** + * The text label: https://material.io/components/buttons#text-button + * + * Should be short. + */ + label: LocalizableText, + + onPress: () => void | Promise, +|}>; + +/** + * A button modeled on Material Design's "text button" concept. + * + * See https://material.io/components/buttons#text-button : + * + * > Text buttons are typically used for less-pronounced actions, including + * > those located + * > + * > - In dialogs + * > - In cards + * > + * > In cards, text buttons help maintain an emphasis on card content. + */ +export default function ZulipTextButton(props: Props) { + const { variant = 'standard', leftMargin, rightMargin, label, onPress } = props; + + const variantStyles = useMemo(() => styleSheetForVariant(variant), [variant]); + + return ( + + + + + ); +} From fddcaf90949bd318b0abdb387cce65d4f9cdf970 Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Wed, 26 May 2021 15:22:01 -0400 Subject: [PATCH 09/12] ZulipTextButton [nfc]: Add TODO for maybe using react-native-paper. As suggested by Wesley [1]. It'll take a bit more work to set up [2] than I have time for right now, but this could be a potentially useful follow-up. [1] https://github.com/zulip/zulip-mobile/pull/4750#discussion_r636555727 [2] https://github.com/zulip/zulip-mobile/pull/4709#discussion_r637105226 --- src/common/ZulipTextButton.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/common/ZulipTextButton.js b/src/common/ZulipTextButton.js index 7174c96e773..010256543b4 100644 --- a/src/common/ZulipTextButton.js +++ b/src/common/ZulipTextButton.js @@ -91,6 +91,11 @@ type Props = $ReadOnly<{| * > * > In cards, text buttons help maintain an emphasis on card content. */ +// TODO: Consider making this a thin wrapper around something like +// react-native-paper's `Button` +// (https://callstack.github.io/react-native-paper/button.html), encoding +// things like project-specific styles and making any sensible adjustments +// to the interface. export default function ZulipTextButton(props: Props) { const { variant = 'standard', leftMargin, rightMargin, label, onPress } = props; From a240defecb0233b24a4883bf45b532d1e5005731 Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Fri, 14 May 2021 17:51:56 -0400 Subject: [PATCH 10/12] ServerCompatBanner: Add, to nag users connected to servers <2.0.0. This would be around 2-4% of our users, as of 2021-03 [1]. As Greg points out [2], we'd like to try this out on a small number of users and gather any feedback, before expanding it to more users, since it's the kind of message that can make people unhappy. So, even though Zulip's release lifecycle doc [3] says 2.1.0 is the threshold, we'd like to start using that threshold only after the code for this threshold has been out for at least a couple of weeks. And at some time in the future, we'll likely settle on a threshold that's not hard-coded at all; see https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/compatibility.20documentation/near/1174966. [1] https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/emoji.20list/near/1136563 [2] https://github.com/zulip/zulip-mobile/pull/4750#issuecomment-848350317 [3] https://zulip.readthedocs.io/en/4.2/overview/release-lifecycle.html --- src/common/ServerCompatBanner.js | 105 +++++++++++++++++++++++++++ src/main/HomeScreen.js | 2 + static/translations/messages_en.json | 5 ++ 3 files changed, 112 insertions(+) create mode 100644 src/common/ServerCompatBanner.js diff --git a/src/common/ServerCompatBanner.js b/src/common/ServerCompatBanner.js new file mode 100644 index 00000000000..097b7f6cc3a --- /dev/null +++ b/src/common/ServerCompatBanner.js @@ -0,0 +1,105 @@ +/* @flow strict-local */ + +import React from 'react'; +import { View } from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; + +import store from '../boot/store'; +import { createStyleSheet, HALF_COLOR } from '../styles'; +import { useSelector, useDispatch } from '../react-redux'; +import Label from './Label'; +import { getActiveAccount } from '../account/accountsSelectors'; +import { getIsAdmin, getSession } from '../directSelectors'; +import { dismissCompatNotice } from '../session/sessionActions'; +import ZulipTextButton from './ZulipTextButton'; +import { openLinkWithUserPreference } from '../utils/openLink'; + +const styles = createStyleSheet({ + wrapper: { + backgroundColor: HALF_COLOR, + paddingLeft: 16, + paddingRight: 8, + paddingBottom: 8, + }, + textRow: { + flexDirection: 'row', + }, + text: { + marginTop: 16, + lineHeight: 20, + }, + buttonsRow: { + marginTop: 12, + flexDirection: 'row', + justifyContent: 'flex-end', + }, +}); + +type Props = $ReadOnly<{||}>; + +/** + * A "nag banner" saying the server version is unsupported, if so. + * + * Currently just checks if it's less than 2.0.0. In the future, this won't + * have to be so hard-coded; we may use a timer or something. + */ +// Made with somewhat careful attention to +// https://material.io/components/banners. Please consult that before making +// layout changes, and try to make them in a direction that brings us closer +// to those guidelines. +export default function ServerCompatBanner(props: Props) { + const dispatch = useDispatch(); + const hasDismissedServerCompatNotice = useSelector( + state => getSession(state).hasDismissedServerCompatNotice, + ); + const zulipVersion = useSelector(state => getActiveAccount(state).zulipVersion); + const realm = useSelector(state => getActiveAccount(state).realm); + const isAdmin = useSelector(getIsAdmin); + + if (!zulipVersion || zulipVersion.isAtLeast('2.0.0')) { + return null; + } else if (hasDismissedServerCompatNotice) { + return null; + } + + return ( + + + + + { + dispatch(dismissCompatNotice()); + }} + /> + { + openLinkWithUserPreference( + 'https://zulip.readthedocs.io/en/stable/overview/release-lifecycle.html#compatibility-and-upgrading', + store.getState, + ); + }} + /> + + + ); +} diff --git a/src/main/HomeScreen.js b/src/main/HomeScreen.js index 78321afc462..3b312a1effb 100644 --- a/src/main/HomeScreen.js +++ b/src/main/HomeScreen.js @@ -16,6 +16,7 @@ import { doNarrow, navigateToSearch } from '../actions'; import IconUnreadMentions from '../nav/IconUnreadMentions'; import { BRAND_COLOR, createStyleSheet } from '../styles'; import { LoadingBanner } from '../common'; +import ServerCompatBanner from '../common/ServerCompatBanner'; const styles = createStyleSheet({ wrapper: { @@ -68,6 +69,7 @@ class HomeScreen extends PureComponent { }} /> + diff --git a/static/translations/messages_en.json b/static/translations/messages_en.json index cc696fea6b7..37670b86ac6 100644 --- a/static/translations/messages_en.json +++ b/static/translations/messages_en.json @@ -60,6 +60,11 @@ "Mute stream": "Mute stream", "Unmute stream": "Unmute stream", "No Internet connection": "No Internet connection", + "{realm} is running Zulip Server {serverVersionRaw}, which is unsupported. Please upgrade your server as soon as possible.": "{realm} is running Zulip Server {serverVersionRaw}, which is unsupported. Please upgrade your server as soon as possible.", + "{realm} is running Zulip Server {serverVersionRaw}, which is unsupported. Please contact your administrator about upgrading.": "{realm} is running Zulip Server {serverVersionRaw}, which is unsupported. Please contact your administrator about upgrading.", + "Dismiss": "Dismiss", + "Fix now": "Fix now", + "Learn more": "Learn more", "Settings": "Settings", "Night mode": "Night mode", "Open links with in-app browser": "Open links with in-app browser", From 65c3b49f143bfada962b76f640be8ba10fe91d07 Mon Sep 17 00:00:00 2001 From: Greg Price Date: Thu, 27 May 2021 21:32:18 -0700 Subject: [PATCH 11/12] ServerCompatBanner [nfc]: Simplify translatable strings a bit. This avoids presenting translators with extra information that isn't meaningful without context that's absent. It also means if we later tweak the form of version number we use here, we won't have a need to change the string and cause churn in the strings to translate. --- src/common/ServerCompatBanner.js | 8 ++++---- static/translations/messages_en.json | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/common/ServerCompatBanner.js b/src/common/ServerCompatBanner.js index 097b7f6cc3a..23373f0e916 100644 --- a/src/common/ServerCompatBanner.js +++ b/src/common/ServerCompatBanner.js @@ -71,13 +71,13 @@ export default function ServerCompatBanner(props: Props) { isAdmin ? { text: - '{realm} is running Zulip Server {serverVersionRaw}, which is unsupported. Please upgrade your server as soon as possible.', - values: { realm: realm.toString(), serverVersionRaw: zulipVersion.raw() }, + '{realm} is running Zulip Server {serverVersion}, which is unsupported. Please upgrade your server as soon as possible.', + values: { realm: realm.toString(), serverVersion: zulipVersion.raw() }, } : { text: - '{realm} is running Zulip Server {serverVersionRaw}, which is unsupported. Please contact your administrator about upgrading.', - values: { realm: realm.toString(), serverVersionRaw: zulipVersion.raw() }, + '{realm} is running Zulip Server {serverVersion}, which is unsupported. Please contact your administrator about upgrading.', + values: { realm: realm.toString(), serverVersion: zulipVersion.raw() }, } } /> diff --git a/static/translations/messages_en.json b/static/translations/messages_en.json index 37670b86ac6..3b21012f098 100644 --- a/static/translations/messages_en.json +++ b/static/translations/messages_en.json @@ -60,8 +60,8 @@ "Mute stream": "Mute stream", "Unmute stream": "Unmute stream", "No Internet connection": "No Internet connection", - "{realm} is running Zulip Server {serverVersionRaw}, which is unsupported. Please upgrade your server as soon as possible.": "{realm} is running Zulip Server {serverVersionRaw}, which is unsupported. Please upgrade your server as soon as possible.", - "{realm} is running Zulip Server {serverVersionRaw}, which is unsupported. Please contact your administrator about upgrading.": "{realm} is running Zulip Server {serverVersionRaw}, which is unsupported. Please contact your administrator about upgrading.", + "{realm} is running Zulip Server {serverVersion}, which is unsupported. Please upgrade your server as soon as possible.": "{realm} is running Zulip Server {serverVersion}, which is unsupported. Please upgrade your server as soon as possible.", + "{realm} is running Zulip Server {serverVersion}, which is unsupported. Please contact your administrator about upgrading.": "{realm} is running Zulip Server {serverVersion}, which is unsupported. Please contact your administrator about upgrading.", "Dismiss": "Dismiss", "Fix now": "Fix now", "Learn more": "Learn more", From a208713f286da2d748ec5520ec02ac87a7ee6835 Mon Sep 17 00:00:00 2001 From: Greg Price Date: Thu, 27 May 2021 21:44:16 -0700 Subject: [PATCH 12/12] ServerCompatBanner [nfc]: Pull out threshold as constant. This isolates it a bit from the logic, for future edits; it also makes more room for the comment to explain the relationship between this threshold and our actual oldest supported version. --- src/common/ServerCompatBanner.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/common/ServerCompatBanner.js b/src/common/ServerCompatBanner.js index 23373f0e916..37ab067ab72 100644 --- a/src/common/ServerCompatBanner.js +++ b/src/common/ServerCompatBanner.js @@ -14,6 +14,12 @@ import { dismissCompatNotice } from '../session/sessionActions'; import ZulipTextButton from './ZulipTextButton'; import { openLinkWithUserPreference } from '../utils/openLink'; +// In fact the oldest version we currently support is 2.1.0, per our docs: +// https://zulip.readthedocs.io/en/4.2/overview/release-lifecycle.html +// For now we only show this banner for servers older than 2.0.0, though, +// in order to phase that in gradually. +const minSupportedVersion = '2.0.0'; + const styles = createStyleSheet({ wrapper: { backgroundColor: HALF_COLOR, @@ -39,9 +45,6 @@ type Props = $ReadOnly<{||}>; /** * A "nag banner" saying the server version is unsupported, if so. - * - * Currently just checks if it's less than 2.0.0. In the future, this won't - * have to be so hard-coded; we may use a timer or something. */ // Made with somewhat careful attention to // https://material.io/components/banners. Please consult that before making @@ -56,7 +59,7 @@ export default function ServerCompatBanner(props: Props) { const realm = useSelector(state => getActiveAccount(state).realm); const isAdmin = useSelector(getIsAdmin); - if (!zulipVersion || zulipVersion.isAtLeast('2.0.0')) { + if (!zulipVersion || zulipVersion.isAtLeast(minSupportedVersion)) { return null; } else if (hasDismissedServerCompatNotice) { return null;