diff --git a/app/components/block/index.tsx b/app/components/block/index.tsx index 051c189f6d..036474f33d 100644 --- a/app/components/block/index.tsx +++ b/app/components/block/index.tsx @@ -47,7 +47,7 @@ type SectionProps = { disableFooter?: boolean; disableHeader?: boolean; footerText?: SectionText; - headerText: SectionText; + headerText?: SectionText; containerStyles?: StyleProp; headerStyles?: StyleProp; footerStyles?: StyleProp; diff --git a/app/components/block_item/index.tsx b/app/components/block_item/index.tsx deleted file mode 100644 index 0e4963d33e..0000000000 --- a/app/components/block_item/index.tsx +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {ReactElement, useCallback, useMemo} from 'react'; -import {StyleProp, Switch, Text, TextStyle, TouchableOpacity, View, ViewStyle} from 'react-native'; - -import CompassIcon from '@components/compass_icon'; -import {useTheme} from '@context/theme'; -import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; -import {typography} from '@utils/typography'; - -const ActionTypes = { - ARROW: 'arrow', - DEFAULT: 'default', - TOGGLE: 'toggle', - SELECT: 'select', -}; - -const getStyleSheet = makeStyleSheetFromTheme((theme) => { - return { - container: { - flexDirection: 'row', - alignItems: 'center', - }, - singleContainer: { - alignItems: 'center', - flex: 1, - flexDirection: 'row', - height: 45, - }, - doubleContainer: { - flex: 1, - flexDirection: 'column', - height: 69, - justifyContent: 'center', - }, - label: { - color: theme.centerChannelColor, - ...typography('Body', 200, 'SemiBold'), - marginLeft: 9, - }, - description: { - color: changeOpacity(theme.centerChannelColor, 0.6), - ...typography('Body', 75, 'Regular'), - marginTop: 3, - }, - arrow: { - color: changeOpacity(theme.centerChannelColor, 0.25), - fontSize: 24, - }, - labelContainer: { - flex: 0, - flexDirection: 'row', - }, - }; -}); - -type Props = { - action: (value: string | boolean) => void; - actionType: string; - actionValue?: string; - containerStyle?: StyleProp; - description?: string | ReactElement; - descriptionStyle?: StyleProp; - icon?: string; - label: string | ReactElement; - labelStyle?: StyleProp; - selected?: boolean; - testID?: string; -} - -const BlockItem = ({ - action, - actionType, - actionValue, - containerStyle, - description, - descriptionStyle, - icon, - label, - labelStyle, - selected, - testID = 'sectionItem', -}: Props) => { - const theme = useTheme(); - const style = getStyleSheet(theme); - - let actionComponent; - if (actionType === ActionTypes.SELECT && selected) { - const selectStyle = [style.arrow, {color: theme.linkColor}]; - actionComponent = ( - - ); - } else if (actionType === ActionTypes.TOGGLE) { - actionComponent = ( - - ); - } else if (actionType === ActionTypes.ARROW) { - actionComponent = ( - - ); - } - - const onPress = useCallback(() => { - action(actionValue || ''); - }, [actionValue, action]); - - const labelStyles = useMemo(() => { - if (icon) { - return [style.label, {marginLeft: 4}]; - } - return [style.label, labelStyle]; - }, [Boolean(icon), style]); - - const component = ( - - - - {Boolean(icon) && ( - - )} - - {label} - - - - {description} - - - {actionComponent} - - ); - - if (actionType === ActionTypes.DEFAULT || actionType === ActionTypes.SELECT || actionType === ActionTypes.ARROW) { - return ( - - {component} - - ); - } - - return component; -}; - -export default BlockItem; diff --git a/app/components/option_item/index.tsx b/app/components/option_item/index.tsx index 956744c327..bed915ef88 100644 --- a/app/components/option_item/index.tsx +++ b/app/components/option_item/index.tsx @@ -2,7 +2,7 @@ // See LICENSE.txt for license information. import React, {useCallback} from 'react'; -import {Switch, Text, TouchableOpacity, View} from 'react-native'; +import {StyleProp, Switch, Text, TouchableOpacity, View, ViewStyle} from 'react-native'; import CompassIcon from '@components/compass_icon'; import {useTheme} from '@context/theme'; @@ -20,6 +20,7 @@ type Props = { testID?: string; type: OptionType; value?: string; + containerStyle?: StyleProp; } const OptionType = { @@ -79,7 +80,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { const OptionItem = ({ action, description, destructive, icon, info, label, selected, - testID = 'optionItem', type, value, + testID = 'optionItem', type, value, containerStyle, }: Props) => { const theme = useTheme(); const styles = getStyleSheet(theme); @@ -119,7 +120,7 @@ const OptionItem = ({ const component = ( diff --git a/app/constants/screens.ts b/app/constants/screens.ts index 601f8b0457..46c6d4188b 100644 --- a/app/constants/screens.ts +++ b/app/constants/screens.ts @@ -43,6 +43,7 @@ export const SELECT_TEAM = 'SelectTeam'; export const SERVER = 'Server'; export const SETTINGS = 'Settings'; export const SETTINGS_DISPLAY = 'SettingsDisplay'; +export const SETTINGS_DISPLAY_THEME = 'SettingsDisplayTheme'; export const SETTINGS_NOTIFICATION = 'SettingsNotification'; export const SETTINGS_NOTIFICATION_AUTO_RESPONDER = 'SettingsNotificationAutoResponder'; export const SETTINGS_NOTIFICATION_MENTION = 'SettingsNotificationMention'; @@ -97,6 +98,7 @@ export default { SERVER, SETTINGS, SETTINGS_DISPLAY, + SETTINGS_DISPLAY_THEME, SETTINGS_NOTIFICATION, SETTINGS_NOTIFICATION_AUTO_RESPONDER, SETTINGS_NOTIFICATION_MENTION, diff --git a/app/queries/servers/system.ts b/app/queries/servers/system.ts index da1e4d6dab..62ae4faabc 100644 --- a/app/queries/servers/system.ts +++ b/app/queries/servers/system.ts @@ -397,20 +397,19 @@ export const observeOnlyUnreads = (database: Database) => { ); }; -export const observeAllowedThemes = (database: Database) => { +export const observeAllowedThemesKeys = (database: Database) => { const defaultThemeKeys = Object.keys(Preferences.THEMES); return observeConfigValue(database, 'AllowedThemes').pipe( switchMap((allowedThemes) => { let acceptableThemes = defaultThemeKeys; - if (allowedThemes) { - const allowedThemeKeys = (allowedThemes || '').split(',').filter(String); + const allowedThemeKeys = (allowedThemes ?? '').split(',').filter(String); if (allowedThemeKeys.length) { acceptableThemes = defaultThemeKeys.filter((k) => allowedThemeKeys.includes(k)); } } - return acceptableThemes; + return of$(acceptableThemes); }), ); }; diff --git a/app/screens/create_or_edit_channel/channel_info_form.tsx b/app/screens/create_or_edit_channel/channel_info_form.tsx index aa61b004b6..cc8c8648f4 100644 --- a/app/screens/create_or_edit_channel/channel_info_form.tsx +++ b/app/screens/create_or_edit_channel/channel_info_form.tsx @@ -17,11 +17,11 @@ import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view'; import {SafeAreaView} from 'react-native-safe-area-context'; import Autocomplete from '@components/autocomplete'; -import BlockItem from '@components/block_item'; import ErrorText from '@components/error_text'; import FloatingTextInput from '@components/floating_text_input_label'; import FormattedText from '@components/formatted_text'; import Loading from '@components/loading'; +import OptionItem from '@components/option_item'; import {General, Channel} from '@constants'; import {useTheme} from '@context/theme'; import {useIsTablet} from '@hooks/device'; @@ -244,12 +244,12 @@ export default function ChannelInfoForm({ > {showSelector && ( - diff --git a/app/screens/index.tsx b/app/screens/index.tsx index a8e69d9b3a..cbb27f30db 100644 --- a/app/screens/index.tsx +++ b/app/screens/index.tsx @@ -61,14 +61,10 @@ Navigation.setLazyComponentRegistrator((screenName) => { screen = withServerDatabase(require('@screens/apps_form').default); break; case Screens.BOTTOM_SHEET: - screen = withServerDatabase( - require('@screens/bottom_sheet').default, - ); + screen = withServerDatabase(require('@screens/bottom_sheet').default); break; case Screens.BROWSE_CHANNELS: - screen = withServerDatabase( - require('@screens/browse_channels').default, - ); + screen = withServerDatabase(require('@screens/browse_channels').default); break; case Screens.CHANNEL: screen = withServerDatabase(require('@screens/channel').default); @@ -83,14 +79,10 @@ Navigation.setLazyComponentRegistrator((screenName) => { screen = withServerDatabase(require('@screens/create_or_edit_channel').default); break; case Screens.CUSTOM_STATUS: - screen = withServerDatabase( - require('@screens/custom_status').default, - ); + screen = withServerDatabase(require('@screens/custom_status').default); break; case Screens.CUSTOM_STATUS_CLEAR_AFTER: - screen = withServerDatabase( - require('@screens/custom_status_clear_after').default, - ); + screen = withServerDatabase(require('@screens/custom_status_clear_after').default); break; case Screens.CREATE_DIRECT_MESSAGE: screen = withServerDatabase(require('@screens/create_direct_message').default); @@ -99,9 +91,7 @@ Navigation.setLazyComponentRegistrator((screenName) => { screen = withServerDatabase(require('@screens/edit_post').default); break; case Screens.EDIT_PROFILE: - screen = withServerDatabase( - require('@screens/edit_profile').default, - ); + screen = withServerDatabase(require('@screens/edit_profile').default); break; case Screens.EDIT_SERVER: screen = withIntl(require('@screens/edit_server').default); @@ -125,8 +115,7 @@ Navigation.setLazyComponentRegistrator((screenName) => { screen = withServerDatabase(require('@screens/interactive_dialog').default); break; case Screens.IN_APP_NOTIFICATION: { - const notificationScreen = - require('@screens/in_app_notification').default; + const notificationScreen = require('@screens/in_app_notification').default; Navigation.registerComponent(Screens.IN_APP_NOTIFICATION, () => Platform.select({ default: notificationScreen, @@ -154,9 +143,7 @@ Navigation.setLazyComponentRegistrator((screenName) => { screen = withServerDatabase(require('@screens/pinned_messages').default); break; case Screens.POST_OPTIONS: - screen = withServerDatabase( - require('@screens/post_options').default, - ); + screen = withServerDatabase(require('@screens/post_options').default); break; case Screens.REACTIONS: screen = withServerDatabase(require('@screens/reactions').default); @@ -164,6 +151,12 @@ Navigation.setLazyComponentRegistrator((screenName) => { case Screens.SETTINGS: screen = withServerDatabase(require('@screens/settings').default); break; + case Screens.SETTINGS_DISPLAY: + screen = withServerDatabase(require('@screens/settings/display').default); + break; + case Screens.SETTINGS_DISPLAY_THEME: + screen = withServerDatabase(require('@screens/settings/display_theme').default); + break; case Screens.SETTINGS_NOTIFICATION: screen = withServerDatabase(require('@screens/settings/notifications').default); break; diff --git a/app/screens/settings/display/display.tsx b/app/screens/settings/display/display.tsx index 7d84bf6d81..91976f1328 100644 --- a/app/screens/settings/display/display.tsx +++ b/app/screens/settings/display/display.tsx @@ -2,11 +2,15 @@ // See LICENSE.txt for license information. import React from 'react'; +import {useIntl} from 'react-intl'; import {Alert, Platform, ScrollView, View} from 'react-native'; import {SafeAreaView} from 'react-native-safe-area-context'; +import {Screens} from '@constants'; import {useTheme} from '@context/theme'; +import {goToScreen} from '@screens/navigation'; import SettingOption from '@screens/settings/setting_option'; +import {preventDoubleTap} from '@utils/tap'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; const getStyleSheet = makeStyleSheetFromTheme((theme) => { @@ -39,13 +43,20 @@ type DisplayProps = { const Display = ({isTimezoneEnabled, isThemeSwitchingEnabled}: DisplayProps) => { const theme = useTheme(); const styles = getStyleSheet(theme); - + const intl = useIntl(); const onPressHandler = () => { return Alert.alert( 'The functionality you are trying to use has not yet been implemented.', ); }; + const goToThemeSettings = preventDoubleTap(() => { + const screen = Screens.SETTINGS_DISPLAY_THEME; + const title = intl.formatMessage({id: 'display_settings.theme', defaultMessage: 'Theme'}); + + goToScreen(screen, title); + }); + return ( {isThemeSwitchingEnabled && ( )} { const isTimezoneEnabled = observeConfigBooleanValue(database, 'ExperimentalTimezone'); const allowsThemeSwitching = observeConfigBooleanValue(database, 'EnableThemeSelection'); - const allowedThemes = observeAllowedThemes(database); + const allowedThemeKeys = observeAllowedThemesKeys(database); - const isThemeSwitchingEnabled = combineLatest([allowsThemeSwitching, allowedThemes]).pipe( + const isThemeSwitchingEnabled = combineLatest([allowsThemeSwitching, allowedThemeKeys]).pipe( switchMap(([ts, ath]) => { return of$(ts && ath.length > 1); }), diff --git a/app/screens/settings/display_theme/custom_theme.tsx b/app/screens/settings/display_theme/custom_theme.tsx new file mode 100644 index 0000000000..95c19b53b9 --- /dev/null +++ b/app/screens/settings/display_theme/custom_theme.tsx @@ -0,0 +1,51 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {useIntl} from 'react-intl'; + +import Block from '@components/block'; +import OptionItem from '@components/option_item'; +import {useTheme} from '@context/theme'; +import {makeStyleSheetFromTheme} from '@utils/theme'; +import {typography} from '@utils/typography'; + +const getStyleSheet = makeStyleSheetFromTheme((theme) => { + return { + label: { + color: theme.centerChannelColor, + ...typography('Body', 200), + }, + containerStyles: { + paddingHorizontal: 16, + }, + }; +}); + +type CustomThemeProps = { + customTheme: Theme; + setTheme: (themeKey: string) => void; +} + +const CustomTheme = ({customTheme, setTheme}: CustomThemeProps) => { + const theme = useTheme(); + const styles = getStyleSheet(theme); + const intl = useIntl(); + + return ( + + + + ); +}; + +export default CustomTheme; diff --git a/app/screens/settings/display_theme/display_theme.tsx b/app/screens/settings/display_theme/display_theme.tsx new file mode 100644 index 0000000000..8ce40600c3 --- /dev/null +++ b/app/screens/settings/display_theme/display_theme.tsx @@ -0,0 +1,80 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React, {useCallback, useEffect, useState} from 'react'; +import {ScrollView, View} from 'react-native'; + +import {savePreference} from '@actions/remote/preference'; +import {Preferences} from '@constants'; +import {useServerUrl} from '@context/server'; +import {useTheme} from '@context/theme'; +import CustomTheme from '@screens/settings/display_theme/custom_theme'; +import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; + +import {ThemeTiles} from './theme_tiles'; + +const getStyleSheet = makeStyleSheetFromTheme((theme) => { + return { + container: { + flex: 1, + }, + wrapper: { + backgroundColor: changeOpacity(theme.centerChannelColor, 0.06), + flex: 1, + paddingTop: 35, + }, + }; +}); + +type DisplayThemeProps = { + allowedThemeKeys: string[]; + currentTeamId: string; + currentUserId: string; +} + +const DisplayTheme = ({allowedThemeKeys, currentTeamId, currentUserId}: DisplayThemeProps) => { + const serverUrl = useServerUrl(); + const theme = useTheme(); + const [customTheme, setCustomTheme] = useState(); + + const styles = getStyleSheet(theme); + + useEffect(() => { + if (theme.type === 'custom') { + setCustomTheme(theme); + } + }, []); + + const updateTheme = useCallback((selectedThemeKey: string) => { + const selectedTheme = allowedThemeKeys.find((tk) => tk === selectedThemeKey); + if (!selectedTheme) { + return; + } + const pref: PreferenceType = { + category: Preferences.CATEGORY_THEME, + name: currentTeamId, + user_id: currentUserId, + value: JSON.stringify(Preferences.THEMES[selectedTheme]), + }; + savePreference(serverUrl, [pref]); + }, [serverUrl, allowedThemeKeys, currentTeamId]); + + return ( + + + + {customTheme && ( + + )} + + + ); +}; + +export default DisplayTheme; diff --git a/app/screens/settings/display_theme/index.ts b/app/screens/settings/display_theme/index.ts new file mode 100644 index 0000000000..374998d869 --- /dev/null +++ b/app/screens/settings/display_theme/index.ts @@ -0,0 +1,27 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider'; +import withObservables from '@nozbe/with-observables'; + +import { + observeAllowedThemesKeys, + observeCurrentTeamId, + observeCurrentUserId, +} from '@queries/servers/system'; +import {WithDatabaseArgs} from '@typings/database/database'; + +import DisplayTheme from './display_theme'; + +const enhanced = withObservables([], ({database}: WithDatabaseArgs) => { + const currentTeamId = observeCurrentTeamId(database); + const currentUserId = observeCurrentUserId(database); + + return { + allowedThemeKeys: observeAllowedThemesKeys(database), + currentTeamId, + currentUserId, + }; +}); + +export default withDatabase(enhanced(DisplayTheme)); diff --git a/app/screens/settings/display_theme/theme_thumbnail.tsx b/app/screens/settings/display_theme/theme_thumbnail.tsx new file mode 100644 index 0000000000..647bb619da --- /dev/null +++ b/app/screens/settings/display_theme/theme_thumbnail.tsx @@ -0,0 +1,275 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import Svg, {Rect, G, Circle} from 'react-native-svg'; + +import {changeOpacity} from '@utils/theme'; + +type ThemeThumbnailProps = { + borderColorBase: string; + borderColorMix: string; + theme: Theme; + width: number; +} + +const ThemeThumbnail = ({borderColorBase, borderColorMix, theme, width}: ThemeThumbnailProps): JSX.Element => { + // the original height of the thumbnail + const baseWidth = 180; + const baseHeight = 134; + + // calculate actual height proportionally to base size + const height = Math.round((width * baseHeight) / baseWidth); + + // convenience values of various sub elements of the thumbnail + const sidebarWidth = 80; + const postsContainerWidth = 100; + const spacing = 8; + const rowHeight = 6; + const rowRadius = rowHeight / 2; + const postInputHeight = 10; + const postWidth = postsContainerWidth - (spacing * 2); + const channelNameWidth = sidebarWidth - (spacing * 3) - (rowHeight * 2); + const buttonWidth = postsContainerWidth - (spacing * 8); + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default ThemeThumbnail; diff --git a/app/screens/settings/display_theme/theme_tiles.tsx b/app/screens/settings/display_theme/theme_tiles.tsx new file mode 100644 index 0000000000..33673a4b4c --- /dev/null +++ b/app/screens/settings/display_theme/theme_tiles.tsx @@ -0,0 +1,149 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React, {useCallback, useMemo} from 'react'; +import {Text, TouchableOpacity, useWindowDimensions, View} from 'react-native'; + +import CompassIcon from '@components/compass_icon'; +import {Preferences} from '@constants'; +import {useTheme} from '@context/theme'; +import {useIsTablet} from '@hooks/device'; +import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; +import {typography} from '@utils/typography'; + +import ThemeThumbnail from './theme_thumbnail'; + +const TILE_PADDING = 8; + +const getStyleSheet = makeStyleSheetFromTheme((theme) => { + return { + container: { + flexDirection: 'column', + padding: TILE_PADDING, + marginTop: 8, + }, + imageWrapper: { + position: 'relative', + alignItems: 'flex-start', + marginBottom: 12, + }, + thumbnail: { + resizeMode: 'stretch', + }, + check: { + position: 'absolute', + right: 5, + bottom: 5, + color: theme.sidebarTextActiveBorder, + }, + label: { + color: theme.centerChannelColor, + ...typography('Body', 200), + }, + tilesContainer: { + marginBottom: 30, + paddingLeft: 8, + flexDirection: 'row', + flexWrap: 'wrap', + backgroundColor: theme.centerChannelBg, + borderTopWidth: 1, + borderBottomWidth: 1, + borderTopColor: changeOpacity(theme.centerChannelColor, 0.1), + borderBottomColor: changeOpacity(theme.centerChannelColor, 0.1), + }, + }; +}); + +type ThemeTileProps = { + action: (v: string) => void; + actionValue: string; + activeTheme: Theme; + label: React.ReactElement; + selected: boolean; + theme: Theme; +}; +export const ThemeTile = ({ + action, + actionValue, + activeTheme, + label, + selected, + theme, +}: ThemeTileProps) => { + const isTablet = useIsTablet(); + const style = getStyleSheet(activeTheme); + const {width: deviceWidth} = useWindowDimensions(); + + const layoutStyle = useMemo(() => { + const tilesPerLine = isTablet ? 4 : 2; + const fullWidth = isTablet ? deviceWidth - 40 : deviceWidth; + + return { + container: { + width: (fullWidth / tilesPerLine) - TILE_PADDING, + }, + thumbnail: { + width: (fullWidth / tilesPerLine) - (TILE_PADDING + 16), + }, + }; + }, [isTablet, deviceWidth]); + + const onPressHandler = useCallback(() => { + action(actionValue); + }, [action, actionValue]); + + return ( + + + + {selected && ( + + )} + + {label} + + ); +}; + +type ThemeTilesProps = { + allowedThemeKeys: string[]; + onThemeChange: (v: string) => void; +} +export const ThemeTiles = ({allowedThemeKeys, onThemeChange}: ThemeTilesProps) => { + const theme = useTheme(); + + const styles = getStyleSheet(theme); + return ( + + { + allowedThemeKeys.map((themeKey: string) => ( + + {themeKey} + + )} + action={onThemeChange} + actionValue={themeKey} + selected={theme.type?.toLowerCase() === themeKey.toLowerCase()} + theme={Preferences.THEMES[themeKey]} + activeTheme={theme} + /> + )) + } + + ); +}; diff --git a/app/screens/settings/notification_auto_responder/notification_auto_responder.tsx b/app/screens/settings/notification_auto_responder/notification_auto_responder.tsx index b32a84b4ff..fa831fd023 100644 --- a/app/screens/settings/notification_auto_responder/notification_auto_responder.tsx +++ b/app/screens/settings/notification_auto_responder/notification_auto_responder.tsx @@ -6,9 +6,9 @@ import {useIntl} from 'react-intl'; import {View} from 'react-native'; import {Edge, SafeAreaView} from 'react-native-safe-area-context'; -import BlockItem from '@components/block_item'; import FloatingTextInput from '@components/floating_text_input_label'; import FormattedText from '@components/formatted_text'; +import OptionItem from '@components/option_item'; import {General} from '@constants'; import {useTheme} from '@context/theme'; import {t} from '@i18n'; @@ -108,15 +108,10 @@ const NotificationAutoResponder = ({currentUser}: NotificationAutoResponderProps - } + diff --git a/app/screens/settings/notification_mention/mention_settings.tsx b/app/screens/settings/notification_mention/mention_settings.tsx index 0cf7534112..3c9bba4c3e 100644 --- a/app/screens/settings/notification_mention/mention_settings.tsx +++ b/app/screens/settings/notification_mention/mention_settings.tsx @@ -6,7 +6,7 @@ import {useIntl} from 'react-intl'; import {Alert, View} from 'react-native'; import Block from '@components/block'; -import BlockItem from '@components/block_item'; +import OptionItem from '@components/option_item'; import {useTheme} from '@context/theme'; import {t} from '@i18n'; import UserModel from '@typings/database/models/servers/user'; @@ -113,52 +113,44 @@ const MentionSettings = ({currentUser, mentionKeys}: MentionSectionProps) => { > { Boolean(currentUser?.firstName) && ( <> - ) } {Boolean(currentUser?.username) && ( - )} - - ); diff --git a/app/screens/settings/notification_mention/reply_settings.tsx b/app/screens/settings/notification_mention/reply_settings.tsx index 9f86ca8087..20970e5e47 100644 --- a/app/screens/settings/notification_mention/reply_settings.tsx +++ b/app/screens/settings/notification_mention/reply_settings.tsx @@ -6,7 +6,7 @@ import {useIntl} from 'react-intl'; import {View} from 'react-native'; import Block from '@components/block'; -import BlockItem from '@components/block_item'; +import OptionItem from '@components/option_item'; import {useTheme} from '@context/theme'; import {t} from '@i18n'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; @@ -52,33 +52,30 @@ const ReplySettings = () => { headerText={replyHeaderText} headerStyles={styles.upperCase} > - - - diff --git a/app/screens/settings/notification_push/push_send.tsx b/app/screens/settings/notification_push/push_send.tsx index 369b7cdc26..0c7186cb83 100644 --- a/app/screens/settings/notification_push/push_send.tsx +++ b/app/screens/settings/notification_push/push_send.tsx @@ -6,8 +6,8 @@ import {useIntl} from 'react-intl'; import {View} from 'react-native'; import Block from '@components/block'; -import BlockItem from '@components/block_item'; import FormattedText from '@components/formatted_text'; +import OptionItem from '@components/option_item'; import {useTheme} from '@context/theme'; import {t} from '@i18n'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; @@ -37,6 +37,9 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { paddingHorizontal: 15, paddingVertical: 10, }, + container: { + paddingHorizontal: 8, + }, }; }); @@ -57,34 +60,34 @@ const MobileSendPush = ({sendPushNotifications, pushStatus, setMobilePushPref}: > {sendPushNotifications && <> - - - } diff --git a/app/screens/settings/notification_push/push_status.tsx b/app/screens/settings/notification_push/push_status.tsx index 7de54de17c..8e6fed3d84 100644 --- a/app/screens/settings/notification_push/push_status.tsx +++ b/app/screens/settings/notification_push/push_status.tsx @@ -6,7 +6,7 @@ import {useIntl} from 'react-intl'; import {View} from 'react-native'; import Block from '@components/block'; -import BlockItem from '@components/block_item'; +import OptionItem from '@components/option_item'; import {useTheme} from '@context/theme'; import {t} from '@i18n'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; @@ -32,6 +32,9 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { ...typography('Body', 100, 'Regular'), }, + container: { + paddingHorizontal: 8, + }, }; }); @@ -49,31 +52,31 @@ const MobilePushStatus = ({pushStatus, setMobilePushStatus}: MobilePushStatusPro headerText={headerText} headerStyles={styles.upperCase} > - - - ); diff --git a/app/screens/settings/notification_push/push_thread.tsx b/app/screens/settings/notification_push/push_thread.tsx index 7a0f3275fa..31aab707fe 100644 --- a/app/screens/settings/notification_push/push_thread.tsx +++ b/app/screens/settings/notification_push/push_thread.tsx @@ -2,11 +2,11 @@ // See LICENSE.txt for license information. import React from 'react'; +import {useIntl} from 'react-intl'; import {View} from 'react-native'; import Block from '@components/block'; -import BlockItem from '@components/block_item'; -import FormattedText from '@components/formatted_text'; +import OptionItem from '@components/option_item'; import {useTheme} from '@context/theme'; import {t} from '@i18n'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; @@ -49,6 +49,7 @@ type MobilePushThreadProps = { const MobilePushThread = ({pushThread, onMobilePushThreadChanged}: MobilePushThreadProps) => { const theme = useTheme(); + const intl = useIntl(); const styles = getStyleSheet(theme); return ( @@ -58,17 +59,11 @@ const MobilePushThread = ({pushThread, onMobilePushThreadChanged}: MobilePushThr headerStyles={styles.upperCase} containerStyles={styles.area} > - - )} + diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index b76b9879f7..0c8bfb526a 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -227,6 +227,7 @@ "custom_status.suggestions.working_from_home": "Working from home", "date_separator.today": "Today", "date_separator.yesterday": "Yesterday", + "display_settings.theme": "Theme", "download.error": "Unable to download the file. Try again later", "edit_post.editPost": "Edit the post...", "edit_post.save": "Save", @@ -711,6 +712,7 @@ "unreads.empty.title": "No more unreads", "user.edit_profile.email.auth_service": "Login occurs through {service}. Email cannot be updated. Email address used for notifications is {email}.", "user.edit_profile.email.web_client": "Email must be updated using a web client or desktop application.", + "user.settings.display.custom_theme": "Custom Theme", "user.settings.general.email": "Email", "user.settings.general.field_handled_externally": "Some fields below are handled through your login provider. If you want to change them, you’ll need to do so through your login provider.", "user.settings.general.firstName": "First Name",