diff --git a/src/boot/ThemeProvider.js b/src/boot/ThemeProvider.js index de02eb78d7d..26ea62ce751 100644 --- a/src/boot/ThemeProvider.js +++ b/src/boot/ThemeProvider.js @@ -1,36 +1,34 @@ /* @flow strict-local */ -import React, { PureComponent } from 'react'; +import React from 'react'; +import { useColorScheme } from 'react-native'; import type { Node as React$Node } from 'react'; -import type { ThemeName, Dispatch } from '../types'; -import { connect } from '../react-redux'; +import { useSelector } from '../react-redux'; import { getSettings } from '../directSelectors'; import { themeData, ThemeContext } from '../styles/theme'; import { ZulipStatusBar } from '../common'; type Props = $ReadOnly<{| - dispatch: Dispatch, - theme: ThemeName, children: React$Node, |}>; -class ThemeProvider extends PureComponent { - static defaultProps = { - theme: 'default', - }; +function ThemeProvider(props: Props) { + const theme = useSelector(state => getSettings(state).theme); + const { children } = props; + const colorScheme = useColorScheme(); + let themeToUse = theme; - render() { - const { children, theme } = this.props; - return ( - - - {children} - - ); + if (themeToUse === 'automatic') { + themeToUse = colorScheme === 'light' || colorScheme == null ? 'light' : 'night'; } + + return ( + + + {children} + + ); } -export default connect(state => ({ - theme: getSettings(state).theme, -}))(ThemeProvider); +export default ThemeProvider; diff --git a/src/nav/AppNavigator.js b/src/nav/AppNavigator.js index d95354eeccc..ce87be1a655 100644 --- a/src/nav/AppNavigator.js +++ b/src/nav/AppNavigator.js @@ -26,6 +26,7 @@ import ChatScreen from '../chat/ChatScreen'; import LanguageScreen from '../settings/LanguageScreen'; import PasswordAuthScreen from '../start/PasswordAuthScreen'; import DebugScreen from '../settings/DebugScreen'; +import ThemeScreen from '../settings/ThemeScreen'; import DiagnosticsScreen from '../diagnostics/DiagnosticsScreen'; import VariablesScreen from '../diagnostics/VariablesScreen'; import TimingScreen from '../diagnostics/TimingScreen'; @@ -62,6 +63,7 @@ export type AppNavigatorParamList = {| lightbox: RouteParamsOf, 'create-group': RouteParamsOf, 'invite-users': RouteParamsOf, + theme: RouteParamsOf, diagnostics: RouteParamsOf, variables: RouteParamsOf, timing: RouteParamsOf, @@ -127,6 +129,7 @@ export default function AppNavigator(props: Props) { + diff --git a/src/nav/navActions.js b/src/nav/navActions.js index 266cb589215..c76a3d90c38 100644 --- a/src/nav/navActions.js +++ b/src/nav/navActions.js @@ -77,6 +77,8 @@ export const navigateToLanguage = (): GenericNavigationAction => StackActions.pu export const navigateToCreateGroup = (): GenericNavigationAction => StackActions.push('create-group'); +export const navigateToThemeScreen = (): GenericNavigationAction => StackActions.push('theme'); + export const navigateToDiagnostics = (): GenericNavigationAction => StackActions.push('diagnostics'); diff --git a/src/reduxTypes.js b/src/reduxTypes.js index a73b106a048..9976f737307 100644 --- a/src/reduxTypes.js +++ b/src/reduxTypes.js @@ -261,7 +261,7 @@ export type RealmState = {| // TODO: Stop using the 'default' name. Any 'default' semantics should // only apply the device level, not within the app. See // https://github.com/zulip/zulip-mobile/issues/4009#issuecomment-619280681. -export type ThemeName = 'default' | 'night'; +export type ThemeName = 'default' | 'night' | 'automatic'; export type SettingsState = {| locale: string, diff --git a/src/settings/SettingsScreen.js b/src/settings/SettingsScreen.js index 6c5f7bf49ac..0b23eacfc16 100644 --- a/src/settings/SettingsScreen.js +++ b/src/settings/SettingsScreen.js @@ -6,11 +6,8 @@ import { ScrollView } from 'react-native'; import type { RouteProp } from '../react-navigation'; import type { MainTabsNavigationProp } from '../main/MainTabsScreen'; import * as NavigationService from '../nav/NavigationService'; -import type { Dispatch } from '../types'; import { createStyleSheet } from '../styles'; -import { connect } from '../react-redux'; -import { getSettings } from '../selectors'; -import { OptionButton, OptionRow } from '../common'; +import { OptionButton } from '../common'; import { IconDiagnostics, IconNotifications, @@ -19,7 +16,7 @@ import { IconMoreHorizontal, } from '../common/Icons'; import { - settingsChange, + navigateToThemeScreen, navigateToNotifications, navigateToLanguage, navigateToDiagnostics, @@ -35,27 +32,18 @@ const styles = createStyleSheet({ type Props = $ReadOnly<{| navigation: MainTabsNavigationProp<'settings'>, route: RouteProp<'settings', void>, - - theme: string, - dispatch: Dispatch, |}>; class SettingsScreen extends PureComponent { - handleThemeChange = () => { - const { dispatch, theme } = this.props; - dispatch(settingsChange({ theme: theme === 'default' ? 'night' : 'default' })); - }; - render() { - const { theme } = this.props; - return ( - { + NavigationService.dispatch(navigateToThemeScreen()); + }} /> { } } -export default connect(state => ({ - theme: getSettings(state).theme, -}))(SettingsScreen); +export default SettingsScreen; diff --git a/src/settings/ThemeScreen.js b/src/settings/ThemeScreen.js new file mode 100644 index 00000000000..14e9c16482b --- /dev/null +++ b/src/settings/ThemeScreen.js @@ -0,0 +1,81 @@ +/* @flow strict-local */ +import React from 'react'; +import { View, FlatList } from 'react-native'; + +import type { RouteProp } from '../react-navigation'; +import { useSelector, useDispatch } from '../react-redux'; +import { getSettings } from '../selectors'; +import '../boot/AppEventHandlers'; +import type { AppNavigationProp } from '../nav/AppNavigator'; +import { settingsChange } from '../actions'; +import { Screen, Touchable, OptionDivider, Label } from '../common'; +import { IconDone } from '../common/Icons'; +import { createStyleSheet, BRAND_COLOR, ThemeContext } from '../styles'; + +const styles = createStyleSheet({ + listWrapper: { + flex: 1, + flexDirection: 'column', + }, + name: { + fontWeight: '300', + fontSize: 13, + }, + listItem: { + flexDirection: 'row', + alignItems: 'center', + paddingTop: 12, + paddingBottom: 12, + paddingLeft: 16, + paddingRight: 16, + }, +}); + +type Props = $ReadOnly<{| + navigation: AppNavigationProp<'theme'>, + route: RouteProp<'theme', void>, +|}>; + +const themeData = [ + { + name: 'System Default', + value: 'automatic', + }, + { + name: 'Dark', + value: 'night', + }, + { + name: 'Light', + value: 'default', + }, +]; + +export default function ThemeScreen(props: Props) { + const dispatch = useDispatch(); + const theme = useSelector(state => getSettings(state).theme); + const { color } = React.useContext(ThemeContext); + + const handleThemeChange = item => { + dispatch(settingsChange({ theme: item.value })); + }; + + return ( + + ( + handleThemeChange(item)}> + + + + {theme === item.value && } + + + )} + /> + + ); +}