Skip to content

Commit 33750f8

Browse files
BigHeadCreationsgnprice
authored andcommitted
theme: Split setting/actual; add getThemeToUse as no-op
This is an NFC change except for the calls to `useColorScheme` it adds. We don't yet do anything with the result -- that's coming later in this series of commits -- but it will sometimes introduce a re-render, namely when the OS-level theme changes.
1 parent 3d7c169 commit 33750f8

File tree

9 files changed

+82
-19
lines changed

9 files changed

+82
-19
lines changed

src/boot/OfflineNoticeProvider.js

+17-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
// @flow strict-local
22
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
33
import type { Node } from 'react';
4-
import { AccessibilityInfo, View, Animated, LayoutAnimation, Platform, Easing } from 'react-native';
4+
import {
5+
AccessibilityInfo,
6+
View,
7+
Animated,
8+
LayoutAnimation,
9+
Platform,
10+
Easing,
11+
useColorScheme,
12+
} from 'react-native';
513
import NetInfo from '@react-native-community/netinfo';
614
import { SafeAreaView } from 'react-native-safe-area-context';
715
import type { ViewProps } from 'react-native/Libraries/Components/View/ViewPropTypes';
@@ -10,6 +18,7 @@ import type { DimensionValue } from 'react-native/Libraries/StyleSheet/StyleShee
1018
import * as logging from '../utils/logging';
1119
import { useDispatch, useGlobalSelector } from '../react-redux';
1220
import { getGlobalSession, getGlobalSettings } from '../directSelectors';
21+
import { getThemeToUse } from '../settings/settingsSelectors';
1322
import { useHasStayedTrueForMs, usePrevious } from '../reactUtils';
1423
import type { JSONableDict } from '../utils/jsonable';
1524
import { createStyleSheet } from '../styles';
@@ -148,6 +157,8 @@ const backgroundColorForTheme = (theme: ThemeName): string =>
148157
*/
149158
export function OfflineNoticeProvider(props: ProviderProps): Node {
150159
const theme = useGlobalSelector(state => getGlobalSettings(state).theme);
160+
const osScheme = useColorScheme();
161+
const themeToUse = getThemeToUse(theme, osScheme);
151162
const _ = useContext(TranslationContext);
152163
const isOnline = useGlobalSelector(state => getGlobalSession(state).isOnline);
153164
const shouldShowUncertaintyNotice = useShouldShowUncertaintyNotice();
@@ -249,7 +260,7 @@ export function OfflineNoticeProvider(props: ProviderProps): Node {
249260

250261
// If changing, also change the status bar color in
251262
// OfflineNoticePlaceholder.
252-
backgroundColor: backgroundColorForTheme(theme),
263+
backgroundColor: backgroundColorForTheme(themeToUse),
253264

254265
justifyContent: 'center',
255266
alignItems: 'center',
@@ -262,7 +273,7 @@ export function OfflineNoticeProvider(props: ProviderProps): Node {
262273
},
263274
noticeText: { fontSize: 14 },
264275
}),
265-
[isNoticeVisible, theme],
276+
[isNoticeVisible, themeToUse],
266277
);
267278

268279
/**
@@ -382,6 +393,8 @@ type PlaceholderProps = $ReadOnly<{|
382393
*/
383394
export function OfflineNoticePlaceholder(props: PlaceholderProps): Node {
384395
const theme = useGlobalSelector(state => getGlobalSettings(state).theme);
396+
const osScheme = useColorScheme();
397+
const themeToUse = getThemeToUse(theme, osScheme);
385398
const { style: callerStyle } = props;
386399

387400
const { isNoticeVisible, noticeContentAreaHeight } = useContext(OfflineNoticeContext);
@@ -451,7 +464,7 @@ export function OfflineNoticePlaceholder(props: PlaceholderProps): Node {
451464
isNoticeVisible && (
452465
<ZulipStatusBar
453466
// Should match the notice's surface; see OfflineNoticeProvider.
454-
backgroundColor={backgroundColorForTheme(theme)}
467+
backgroundColor={backgroundColorForTheme(themeToUse)}
455468
/>
456469
)
457470
}

src/boot/ThemeProvider.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22

33
import React from 'react';
44
import type { Node } from 'react';
5+
import { useColorScheme } from 'react-native';
56

67
import { useGlobalSelector } from '../react-redux';
78
import { getGlobalSettings } from '../directSelectors';
89
import { themeData, ThemeContext } from '../styles/theme';
910
import ZulipStatusBar from '../common/ZulipStatusBar';
11+
import { getThemeToUse } from '../settings/settingsSelectors';
1012

1113
type Props = $ReadOnly<{|
1214
children: Node,
@@ -15,8 +17,11 @@ type Props = $ReadOnly<{|
1517
export default function ThemeProvider(props: Props): Node {
1618
const { children } = props;
1719
const theme = useGlobalSelector(state => getGlobalSettings(state).theme);
20+
const osScheme = useColorScheme();
21+
const themeToUse = getThemeToUse(theme, osScheme);
22+
1823
return (
19-
<ThemeContext.Provider value={themeData[theme]}>
24+
<ThemeContext.Provider value={themeData[themeToUse]}>
2025
<ZulipStatusBar />
2126
{children}
2227
</ThemeContext.Provider>

src/boot/ZulipSafeAreaProvider.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
// @flow strict-local
22
import * as React from 'react';
33
import { SafeAreaProvider } from 'react-native-safe-area-context';
4+
import { useColorScheme } from 'react-native';
45

56
import { BRAND_COLOR } from '../styles';
67
import { useGlobalSelector } from '../react-redux';
78
import { getGlobalSettings, getIsHydrated } from '../directSelectors';
89
import { themeData } from '../styles/theme';
10+
import { getThemeToUse } from '../settings/settingsSelectors';
911

1012
type Props = {|
1113
+children: React.Node,
@@ -27,14 +29,18 @@ export default function ZulipSafeAreaProvider(props: Props): React.Node {
2729
//
2830
// We can make this quirk virtually invisible by giving it the background
2931
// color used across the app.
32+
const osScheme = useColorScheme();
33+
3034
const backgroundColor = useGlobalSelector(state => {
3135
if (!getIsHydrated(state)) {
3236
// The only screen we'll be showing at this point is the loading
3337
// screen. Match that screen's background.
3438
return BRAND_COLOR;
3539
}
3640

37-
return themeData[getGlobalSettings(state).theme].backgroundColor;
41+
const theme = getGlobalSettings(state).theme;
42+
const themeToUse = getThemeToUse(theme, osScheme);
43+
return themeData[themeToUse].backgroundColor;
3844
});
3945

4046
return <SafeAreaProvider style={{ backgroundColor }}>{props.children}</SafeAreaProvider>;

src/common/Popup.js

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
/* @flow strict-local */
22
import React, { useContext } from 'react';
33
import type { Node } from 'react';
4-
import { View } from 'react-native';
4+
import { View, useColorScheme } from 'react-native';
55

66
import { ThemeContext, createStyleSheet } from '../styles';
77
import { useGlobalSelector } from '../react-redux';
88
import { getGlobalSettings } from '../directSelectors';
9+
import { getThemeToUse } from '../settings/settingsSelectors';
910

1011
const styles = createStyleSheet({
1112
popup: {
@@ -45,8 +46,12 @@ type Props = $ReadOnly<{|
4546
*/
4647
export default function Popup(props: Props): Node {
4748
const themeContext = useContext(ThemeContext);
49+
const theme = useGlobalSelector(state => getGlobalSettings(state).theme);
50+
const osScheme = useColorScheme();
51+
const themeToUse = getThemeToUse(theme, osScheme);
52+
4853
// TODO(color/theme): find a cleaner way to express this
49-
const isDarkTheme = useGlobalSelector(state => getGlobalSettings(state).theme !== 'default');
54+
const isDarkTheme = themeToUse !== 'default';
5055
return (
5156
<View style={[{ backgroundColor: themeContext.backgroundColor }, styles.popup]}>
5257
<View style={isDarkTheme && styles.overlay}>{props.children}</View>

src/common/ZulipStatusBar.js

+7-2
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22

33
import React from 'react';
44
import type { Node } from 'react';
5-
import { Platform, StatusBar } from 'react-native';
5+
import { Platform, StatusBar, useColorScheme } from 'react-native';
66
// $FlowFixMe[untyped-import]
77
import Color from 'color';
88

99
import type { ThemeName } from '../types';
1010
import { useGlobalSelector } from '../react-redux';
1111
import { foregroundColorFromBackground } from '../utils/color';
1212
import { getGlobalSession, getGlobalSettings } from '../selectors';
13+
import { getThemeToUse } from '../settings/settingsSelectors';
1314

1415
type BarStyle = React$ElementConfig<typeof StatusBar>['barStyle'];
1516

@@ -46,9 +47,13 @@ type Props = $ReadOnly<{|
4647
export default function ZulipStatusBar(props: Props): Node {
4748
const { hidden = false } = props;
4849
const theme = useGlobalSelector(state => getGlobalSettings(state).theme);
50+
const osScheme = useColorScheme();
51+
const themeToUse = getThemeToUse(theme, osScheme);
52+
4953
const orientation = useGlobalSelector(state => getGlobalSession(state).orientation);
5054
const backgroundColor = props.backgroundColor;
51-
const statusBarColor = getStatusBarColor(backgroundColor, theme);
55+
const statusBarColor = getStatusBarColor(backgroundColor, themeToUse);
56+
5257
return (
5358
orientation === 'PORTRAIT' && (
5459
<StatusBar

src/nav/ZulipNavigationContainer.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
/* @flow strict-local */
22
import React, { useContext, useEffect } from 'react';
33
import type { Node } from 'react';
4+
import { useColorScheme } from 'react-native';
45
import { NavigationContainer, DefaultTheme, DarkTheme } from '@react-navigation/native';
56

67
import { useGlobalSelector } from '../react-redux';
78
import { ThemeContext } from '../styles';
89
import * as NavigationService from './NavigationService';
910
import { getGlobalSettings } from '../selectors';
1011
import AppNavigator from './AppNavigator';
12+
import { getThemeToUse } from '../settings/settingsSelectors';
1113

1214
type Props = $ReadOnly<{||}>;
1315

@@ -23,6 +25,8 @@ type Props = $ReadOnly<{||}>;
2325
*/
2426
export default function ZulipAppContainer(props: Props): Node {
2527
const themeName = useGlobalSelector(state => getGlobalSettings(state).theme);
28+
const osScheme = useColorScheme();
29+
const themeToUse = getThemeToUse(themeName, osScheme);
2630

2731
useEffect(
2832
() =>
@@ -36,11 +40,11 @@ export default function ZulipAppContainer(props: Props): Node {
3640

3741
const themeContext = useContext(ThemeContext);
3842

39-
const BaseTheme = themeName === 'night' ? DarkTheme : DefaultTheme;
43+
const BaseTheme = themeToUse === 'night' ? DarkTheme : DefaultTheme;
4044

4145
const theme = {
4246
...BaseTheme,
43-
dark: themeName === 'night',
47+
dark: themeToUse === 'night',
4448
colors: {
4549
...BaseTheme.colors,
4650
primary: themeContext.color,

src/reduxTypes.js

+18-4
Original file line numberDiff line numberDiff line change
@@ -348,11 +348,25 @@ export type RealmState = {|
348348
+serverEmojiData: ServerEmojiData | null,
349349
|};
350350

351-
// TODO: Stop using the 'default' name. Any 'default' semantics should
352-
// only apply the device level, not within the app. See
353-
// https://github.com/zulip/zulip-mobile/issues/4009#issuecomment-619280681.
351+
/**
352+
* The visual theme of the app.
353+
*
354+
* To get a ThemeName from the ThemeSetting, first check the current
355+
* OS theme by calling the React Native hook useColorScheme and pass
356+
* that to the helper function getThemeToUse.
357+
*/
354358
export type ThemeName = 'default' | 'night';
355359

360+
/**
361+
* The theme setting.
362+
*
363+
* This represents the value the user chooses in SettingsScreen.
364+
*
365+
* To determine the actual theme to show the user, use a ThemeName;
366+
* see there for details.
367+
*/
368+
export type ThemeSetting = 'default' | 'night';
369+
356370
/** What browser the user has set to use for opening links in messages.
357371
*
358372
* * embedded: The in-app browser
@@ -392,7 +406,7 @@ export type GlobalSettingsState = $ReadOnly<{
392406
// The user's chosen language, as an IETF BCP 47 language tag.
393407
language: string,
394408

395-
theme: ThemeName,
409+
theme: ThemeSetting,
396410
browser: BrowserPreference,
397411

398412
// TODO cut this? what was it?

src/settings/settingsSelectors.js

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/* @flow strict-local */
2+
import type { ColorSchemeName } from 'react-native/Libraries/Utilities/NativeAppearance';
3+
import type { ThemeSetting, ThemeName } from '../reduxTypes';
4+
5+
export const getThemeToUse = (theme: ThemeSetting, osScheme: ?ColorSchemeName): ThemeName =>
6+
// This is a no-op stub. We'll give it more interesting behavior soon.
7+
theme;

src/start/IosCompliantAppleAuthButton/index.js

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
/* @flow strict-local */
22
import React, { useState, useEffect } from 'react';
33
import type { Node } from 'react';
4-
import { View } from 'react-native';
4+
import { View, useColorScheme } from 'react-native';
55
import type { ViewStyle } from 'react-native/Libraries/StyleSheet/StyleSheet';
66
import * as AppleAuthentication from 'expo-apple-authentication';
77
import { useGlobalSelector } from '../../react-redux';
88

99
import type { SubsetProperties } from '../../generics';
1010
import Custom from './Custom';
1111
import { getGlobalSettings } from '../../selectors';
12+
import { getThemeToUse } from '../../settings/settingsSelectors';
1213

1314
type Props = $ReadOnly<{|
1415
// See `ZulipButton`'s `style` prop, where a comment discusses this
@@ -35,6 +36,9 @@ type Props = $ReadOnly<{|
3536
export default function IosCompliantAppleAuthButton(props: Props): Node {
3637
const { style, onPress } = props;
3738
const theme = useGlobalSelector(state => getGlobalSettings(state).theme);
39+
const osScheme = useColorScheme();
40+
const themeToUse = getThemeToUse(theme, osScheme);
41+
3842
const [isNativeButtonAvailable, setIsNativeButtonAvailable] = useState<boolean | void>(undefined);
3943

4044
useEffect(() => {
@@ -56,7 +60,7 @@ export default function IosCompliantAppleAuthButton(props: Props): Node {
5660
<AppleAuthentication.AppleAuthenticationButton
5761
buttonType={AppleAuthentication.AppleAuthenticationButtonType.SIGN_IN}
5862
buttonStyle={
59-
theme === 'default'
63+
themeToUse === 'default'
6064
? AppleAuthentication.AppleAuthenticationButtonStyle.WHITE_OUTLINE
6165
: AppleAuthentication.AppleAuthenticationButtonStyle.BLACK
6266
}
@@ -66,6 +70,6 @@ export default function IosCompliantAppleAuthButton(props: Props): Node {
6670
/>
6771
);
6872
} else {
69-
return <Custom style={style} onPress={onPress} theme={theme} />;
73+
return <Custom style={style} onPress={onPress} theme={themeToUse} />;
7074
}
7175
}

0 commit comments

Comments
 (0)