From 5d92fae3d61ca0efa91d05bb08f8bcd251ed5eb1 Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Wed, 12 Feb 2020 18:53:29 -0800 Subject: [PATCH 01/11] color: Type return value of foregroundColorFromBackground more precisely. --- src/utils/color.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/color.js b/src/utils/color.js index b6122a7b88f..0bd4b376320 100644 --- a/src/utils/color.js +++ b/src/utils/color.js @@ -2,7 +2,7 @@ import Color from 'color'; import type { ColorValue } from 'react-native/Libraries/StyleSheet/StyleSheetTypes'; -export const foregroundColorFromBackground = (color: ColorValue): string => +export const foregroundColorFromBackground = (color: ColorValue): 'black' | 'white' => Color(color).luminosity() > 0.4 ? 'black' : 'white'; export const colorHashFromString = (name: string): string => { From bfb884a0ec131c9cc02ab664a18d446530c89a5c Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Wed, 12 Feb 2020 17:56:37 -0800 Subject: [PATCH 02/11] loading indicator: Add white loading indicator asset. Also, change the `color` prop of both SpinningProgress and LoadingIndicator to accept three specific strings, 'white', 'black', or 'default'. Previously, the `color` prop was checked against the string '0, 0, 0', and used the black one if true, and the default one otherwise. --- src/common/LoadingIndicator.js | 6 +++--- src/common/SpinningProgress.js | 16 +++++++++++++--- src/start/LoadingScreen.js | 2 +- static/img/spinning-progress-white.png | Bin 0 -> 14597 bytes 4 files changed, 17 insertions(+), 7 deletions(-) create mode 100644 static/img/spinning-progress-white.png diff --git a/src/common/LoadingIndicator.js b/src/common/LoadingIndicator.js index 519a706cc7e..ad36a51b6ec 100644 --- a/src/common/LoadingIndicator.js +++ b/src/common/LoadingIndicator.js @@ -21,13 +21,13 @@ const styles = StyleSheet.create({ }); type Props = $ReadOnly<{| - color: string, + color: 'default' | 'black' | 'white', showLogo: boolean, size: number, |}>; /** - * Renders a loading indicator - light circle and a darker + * Renders a loading indicator - a faint circle and a bold * quarter of a circle spinning around it. Optionally, * a Zulip logo in the center. * @@ -37,7 +37,7 @@ type Props = $ReadOnly<{| */ export default class LoadingIndicator extends PureComponent { static defaultProps = { - color: '82, 194, 175', + color: 'default', showLogo: false, size: 40, }; diff --git a/src/common/SpinningProgress.js b/src/common/SpinningProgress.js index 834c6b02806..92c51a7ff5e 100644 --- a/src/common/SpinningProgress.js +++ b/src/common/SpinningProgress.js @@ -5,9 +5,10 @@ import { Image } from 'react-native'; import AnimatedRotateComponent from '../animation/AnimatedRotateComponent'; import spinningProgressImg from '../../static/img/spinning-progress.png'; import spinningProgressBlackImg from '../../static/img/spinning-progress-black.png'; +import spinningProgressWhiteImg from '../../static/img/spinning-progress-white.png'; type Props = $ReadOnly<{| - color: string, + color: 'default' | 'black' | 'white', size: number, |}>; @@ -17,14 +18,23 @@ type Props = $ReadOnly<{| * * This is a temporary replacement of the ART-based SpinningProgress. * - * @prop color - The color of the circle. Works only for 'black' and 'default'. + * @prop color - The color of the circle. * @prop size - Diameter of the circle in pixels. */ export default class SpinningProgress extends React.PureComponent { render() { const { color, size } = this.props; const style = { width: size, height: size }; - const source = color === '0, 0, 0' ? spinningProgressBlackImg : spinningProgressImg; + const source = (() => { + switch (color) { + case 'white': + return spinningProgressWhiteImg; + case 'black': + return spinningProgressBlackImg; + default: + return spinningProgressImg; + } + })(); return ( diff --git a/src/start/LoadingScreen.js b/src/start/LoadingScreen.js index 41be32a6c1c..f2178af1d04 100644 --- a/src/start/LoadingScreen.js +++ b/src/start/LoadingScreen.js @@ -19,7 +19,7 @@ export default class LoadingScreen extends PureComponent<{||}> { return ( - + ); } diff --git a/static/img/spinning-progress-white.png b/static/img/spinning-progress-white.png new file mode 100644 index 0000000000000000000000000000000000000000..52c80b07be42c610e2c07e8ac41eb2912f80330c GIT binary patch literal 14597 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 U&(G`ry{)M5T1B?%l}XTl0s07cyZ`_I literal 0 HcmV?d00001 From abc0432539054b8b227c51a4a9171459aa346314 Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Tue, 25 Feb 2020 13:58:19 -0800 Subject: [PATCH 03/11] eslint: Disable react-native/no-inline-styles. Some styles are most readable when they are inlined. This may be seen as a performance issue, because we're making new style objects on each render, and sometimes this will result in an unnecessary rerender. But the reality is we have tons of code that does that already, because the lint rule doesn't catch even simple variations. Some of these variations are very commonly used because the style depends on props, state, and context -- so there's not a clear path to turning this rule into something useful. See discussion at https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/no-inline-styles.20rule/near/826995. --- .eslintrc.yaml | 1 - src/webview/MessageList.js | 1 - 2 files changed, 2 deletions(-) diff --git a/.eslintrc.yaml b/.eslintrc.yaml index d425492c6a5..34aca5d10d2 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -106,7 +106,6 @@ rules: # React Native. This plugin isn't included in the airbnb config, and # nothing is enabled by default, so we enable its rules. - react-native/no-inline-styles: error # react-native/no-unused-styles: error # This is buggy on the `this.styles` pattern. # react-native/no-color-literals: error # TODO eliminate these and enable. diff --git a/src/webview/MessageList.js b/src/webview/MessageList.js index 5cbd4e7a59c..9c874d450b6 100644 --- a/src/webview/MessageList.js +++ b/src/webview/MessageList.js @@ -321,7 +321,6 @@ class MessageList extends Component { source={{ baseUrl, html }} originWhitelist={['file://']} onShouldStartLoadWithRequest={onShouldStartLoadWithRequest} - /* eslint-disable react-native/no-inline-styles */ style={{ backgroundColor: this.context.backgroundColor }} ref={webview => { this.webview = webview; From bfdde6fb831bc630450675334f827ddee1c9c771 Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Tue, 25 Feb 2020 16:21:42 -0800 Subject: [PATCH 04/11] nav bar styles [nfc]: Inline the only three uses of styles.navBar. The three uses are different enough to motiviate inlining these styles; the next commits in this series add a loading banner to be used in ChatNavBar but not ModalNavBar or ModalSearchNavBar. In the child, we help along the deprecation of `context.styles`, as in, e.g., a606bf3040, by using the ThemeContext context type. --- src/nav/ChatNavBar.js | 15 +++++++++++++-- src/nav/ModalNavBar.js | 13 ++++++++++++- src/nav/ModalSearchNavBar.js | 12 +++++++++++- src/styles/navStyles.js | 17 +---------------- src/styles/theme.js | 3 --- 5 files changed, 37 insertions(+), 23 deletions(-) diff --git a/src/nav/ChatNavBar.js b/src/nav/ChatNavBar.js index d6d0ad0c010..4341c075df8 100644 --- a/src/nav/ChatNavBar.js +++ b/src/nav/ChatNavBar.js @@ -5,7 +5,7 @@ import { View } from 'react-native'; import type { Dispatch, Narrow } from '../types'; import { connect } from '../react-redux'; -import styles, { BRAND_COLOR } from '../styles'; +import { BRAND_COLOR, NAVBAR_SIZE } from '../styles'; import Title from '../title/Title'; import NavButton from './NavButton'; import { DEFAULT_TITLE_BACKGROUND_COLOR, getTitleBackgroundColor } from '../title/titleSelectors'; @@ -33,7 +33,18 @@ class ChatNavBar extends PureComponent { : foregroundColorFromBackground(backgroundColor); return ( - + { ]; return ( - + {canGoBack && ( { const { styles: contextStyles } = this.context; const { dispatch, autoFocus, searchBarOnChange } = this.props; return ( - + { diff --git a/src/styles/navStyles.js b/src/styles/navStyles.js index 7aa6e024402..040a364260f 100644 --- a/src/styles/navStyles.js +++ b/src/styles/navStyles.js @@ -1,6 +1,5 @@ /* @flow strict-local */ -import type { ThemeColors } from './theme'; -import { BRAND_COLOR, NAVBAR_SIZE } from './constants'; +import { BRAND_COLOR } from './constants'; export const statics = { navWrapper: { @@ -17,18 +16,4 @@ export const statics = { textAlign: 'left', fontSize: 20, }, - navBar: { - borderColor: 'hsla(0, 0%, 50%, 0.25)', - flexDirection: 'row', - height: NAVBAR_SIZE, - alignItems: 'center', - borderBottomWidth: 1, - }, }; - -export default ({ backgroundColor }: ThemeColors) => ({ - navBar: { - ...statics.navBar, - backgroundColor, - }, -}); diff --git a/src/styles/theme.js b/src/styles/theme.js index b20ed4ecaa3..4aad30fab06 100644 --- a/src/styles/theme.js +++ b/src/styles/theme.js @@ -4,7 +4,6 @@ import type { Context } from 'react'; import { StyleSheet } from 'react-native'; import type { ThemeName } from '../types'; -import navStyles from './navStyles'; import miscStyles from './miscStyles'; export type ThemeColors = {| @@ -15,7 +14,6 @@ export type ThemeColors = {| |}; export type AppStyles = $ReadOnly<{| - ...$Call, ...$Call, |}>; @@ -44,7 +42,6 @@ export const ThemeContext: Context = React.createContext(themeColor export const stylesFromTheme = (name: ThemeName) => { const colors = themeColors[name]; return StyleSheet.create({ - ...navStyles(colors), ...miscStyles(colors), }); }; From e07e2dcd26b4819261e9d7f50668b37a33e0dd31 Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Mon, 9 Mar 2020 13:24:55 -0700 Subject: [PATCH 05/11] modal nav bars: Use ThemeContext instead of old-style context. See a606bf3040 for another example. --- src/nav/ModalNavBar.js | 16 +++++++--------- src/nav/ModalSearchNavBar.js | 15 ++++++--------- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/src/nav/ModalNavBar.js b/src/nav/ModalNavBar.js index de8c6e5ba54..07ef74d22ba 100644 --- a/src/nav/ModalNavBar.js +++ b/src/nav/ModalNavBar.js @@ -3,9 +3,11 @@ import React, { PureComponent } from 'react'; import { View } from 'react-native'; -import type { Dispatch, Context, LocalizableText } from '../types'; +import type { Dispatch, LocalizableText } from '../types'; +import type { ThemeColors } from '../styles'; +import styles, { ThemeContext, NAVBAR_SIZE } from '../styles'; import { connect } from '../react-redux'; -import styles, { NAVBAR_SIZE } from '../styles'; + import Label from '../common/Label'; import NavButton from './NavButton'; import { navigateBack } from '../actions'; @@ -17,14 +19,10 @@ type Props = $ReadOnly<{| |}>; class ModalNavBar extends PureComponent { - context: Context; - - static contextTypes = { - styles: () => null, - }; + static contextType = ThemeContext; + context: ThemeColors; render() { - const { styles: contextStyles } = this.context; const { dispatch, canGoBack, title } = this.props; const textStyle = [ styles.navTitle, @@ -40,7 +38,7 @@ class ModalNavBar extends PureComponent { height: NAVBAR_SIZE, alignItems: 'center', borderBottomWidth: 1, - backgroundColor: contextStyles.backgroundColor.backgroundColor, + backgroundColor: this.context.backgroundColor, }, ]} > diff --git a/src/nav/ModalSearchNavBar.js b/src/nav/ModalSearchNavBar.js index 60b6d518e87..976dd28115b 100644 --- a/src/nav/ModalSearchNavBar.js +++ b/src/nav/ModalSearchNavBar.js @@ -2,8 +2,9 @@ import React, { PureComponent } from 'react'; import { View } from 'react-native'; -import type { Dispatch, Context } from '../types'; -import { NAVBAR_SIZE } from '../styles'; +import type { Dispatch } from '../types'; +import type { ThemeColors } from '../styles'; +import { ThemeContext, NAVBAR_SIZE } from '../styles'; import { connect } from '../react-redux'; import SearchInput from '../common/SearchInput'; import NavButton from './NavButton'; @@ -16,14 +17,10 @@ type Props = $ReadOnly<{| |}>; class ModalSearchNavBar extends PureComponent { - context: Context; - - static contextTypes = { - styles: () => null, - }; + static contextType = ThemeContext; + context: ThemeColors; render() { - const { styles: contextStyles } = this.context; const { dispatch, autoFocus, searchBarOnChange } = this.props; return ( { height: NAVBAR_SIZE, alignItems: 'center', borderBottomWidth: 1, - backgroundColor: contextStyles.backgroundColor.backgroundColor, + backgroundColor: this.context.backgroundColor, }} > Date: Tue, 25 Feb 2020 13:45:14 -0800 Subject: [PATCH 06/11] redux types [nfc]: Document state.session.loading with a comment. --- src/session/sessionReducer.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/session/sessionReducer.js b/src/session/sessionReducer.js index 1f515159c69..a96a447caa2 100644 --- a/src/session/sessionReducer.js +++ b/src/session/sessionReducer.js @@ -37,7 +37,15 @@ export type SessionState = {| isActive: boolean, isHydrated: boolean, lastNarrow: ?Narrow, + + /** + * Whether the /register request is in progress. + * + * This happens on startup, or on re-init following a dead event + * queue after 10 minutes of inactivity. + */ loading: boolean, + needsInitialFetch: boolean, orientation: Orientation, outboxSending: boolean, From 6d6dec1e9b97e9e1d6827837577c6f56b37c37ed Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Tue, 25 Feb 2020 16:31:55 -0800 Subject: [PATCH 07/11] LoadingBanner: Add, to show loading state alongside latest stale data. For #3387, provide the component to be used to show a loading banner during the /register request. This will be much improved with an animation, but progress is blocked by #3899. One idea is to give the exit animation a shorter duration than the entrance animation, to give the impression that we've been awaiting updates just as attentively as the user, and that we're eager to show the updates and get out of the way immediately. --- src/common/LoadingBanner.js | 81 +++++++++++++++++++++++++++++++++++++ src/common/index.js | 1 + 2 files changed, 82 insertions(+) create mode 100644 src/common/LoadingBanner.js diff --git a/src/common/LoadingBanner.js b/src/common/LoadingBanner.js new file mode 100644 index 00000000000..9f75daa6540 --- /dev/null +++ b/src/common/LoadingBanner.js @@ -0,0 +1,81 @@ +/* @flow strict-local */ + +import React, { PureComponent } from 'react'; +import { StyleSheet, View } from 'react-native'; + +import type { Dispatch } from '../types'; +import { connect } from '../react-redux'; +import { getLoading } from '../selectors'; +import { Label, LoadingIndicator } from '.'; +import type { ThemeColors } from '../styles'; +import { ThemeContext } from '../styles'; + +const key = 'LoadingBanner'; + +const styles = StyleSheet.create({ + block: { + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + backgroundColor: 'hsl(6, 98%, 57%)', + }, + none: { display: 'none' }, +}); + +type SelectorProps = $ReadOnly<{| + loading: boolean, +|}>; + +type Props = $ReadOnly<{| + spinnerColor?: 'black' | 'white' | 'default', + textColor?: string, + backgroundColor?: string, + + dispatch: Dispatch, + ...SelectorProps, +|}>; + +/** + * Display a notice that the app is connecting to the server, when appropriate. + */ +class LoadingBanner extends PureComponent { + static contextType = ThemeContext; + context: ThemeColors; + + render() { + if (!this.props.loading) { + return ; + } + const { + spinnerColor = 'default', + textColor = this.context.color, + backgroundColor = this.context.backgroundColor, + } = this.props; + const style = { + width: '100%', + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + backgroundColor, + }; + return ( + + + + + + ); + } +} + +export default connect(state => ({ + loading: getLoading(state), +}))(LoadingBanner); diff --git a/src/common/index.js b/src/common/index.js index 7186a688ba6..1e7592322f8 100644 --- a/src/common/index.js +++ b/src/common/index.js @@ -15,6 +15,7 @@ export { default as KeyboardAvoider } from './KeyboardAvoider'; export { default as Label } from './Label'; export { default as LineSeparator } from './LineSeparator'; export { default as LoadingIndicator } from './LoadingIndicator'; +export { default as LoadingBanner } from './LoadingBanner'; export { default as Logo } from './Logo'; export { default as OfflineNotice } from './OfflineNotice'; export { default as OptionButton } from './OptionButton'; From e7181fa91499a5994654eb4cea11517ad4a1ae2e Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Wed, 12 Feb 2020 18:29:26 -0800 Subject: [PATCH 08/11] Screen.js: Default to using LoadingBanner, suppress for some callers. Not all screens that use Screen need the LoadingBanner, since some of them don't rely on data coming in through the event system. Suppressed on: - AccountPickScreen - AuthScreen - DevAuthScreen - PasswordAuthScreen - RealmScreen Not suppressed on: - AccountDetailsScreen - GroupDetailsScreen - DiagnosticsScreen - VariablesScreen - DebugScreen - LanguageScreen - LegalScreen - NotificationsScreen - MessageReactionListScreen - TopicListScreen - InviteUsersScreen - CreateGroupScreen - UserStatusScreen - UsersScreen - EmojiPickerScreen - SearchMessagesScreen - CreateStreamScreen - EditStreamScreen - StreamScreen --- src/account/AccountPickScreen.js | 8 +++++++- src/common/Screen.js | 5 +++++ src/start/AuthScreen.js | 2 +- src/start/DevAuthScreen.js | 2 +- src/start/PasswordAuthScreen.js | 8 +++++++- src/start/RealmScreen.js | 1 + 6 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/account/AccountPickScreen.js b/src/account/AccountPickScreen.js index 4510767bcfb..1907de29c04 100644 --- a/src/account/AccountPickScreen.js +++ b/src/account/AccountPickScreen.js @@ -49,7 +49,13 @@ class AccountPickScreen extends PureComponent { const { accounts, dispatch } = this.props; return ( - + {accounts.length === 0 && } void, + shouldShowLoadingBanner: boolean, canGoBack: boolean, +title: LocalizableText, @@ -85,6 +87,7 @@ class Screen extends PureComponent { search: false, autoFocus: false, searchBarOnChange: (text: string) => {}, + shouldShowLoadingBanner: true, canGoBack: true, title: '', @@ -104,6 +107,7 @@ class Screen extends PureComponent { searchBarOnChange, style, title, + shouldShowLoadingBanner, } = this.props; const { styles: contextStyles } = this.context; @@ -116,6 +120,7 @@ class Screen extends PureComponent { )} + {shouldShowLoadingBanner && } { const { serverSettings } = this.props.navigation.state.params; return ( - + { const { directAdmins, directUsers, error, progress } = this.state; return ( - + {progress && } {!!error && } diff --git a/src/start/PasswordAuthScreen.js b/src/start/PasswordAuthScreen.js index 636cd255299..9b4dbc5128f 100644 --- a/src/start/PasswordAuthScreen.js +++ b/src/start/PasswordAuthScreen.js @@ -91,7 +91,13 @@ class PasswordAuthScreen extends PureComponent { || (requireEmailFormat && !isValidEmailFormat(email)); return ( - + { padding centerContent keyboardShouldPersistTaps="always" + shouldShowLoadingBanner={false} >