forked from Ivasoft/mattermost-mobile
MM-39711 - Gekidou - Theme functionality (#6327)
* added chevron to menu item component * starting with the skeleton * starting with the skeleton * starting with the skeleton * starting with the skeleton * remove extra line * tested on tablets * some corrections * corrections as per review * starting with notification skeleton * attached notification settings to navigation * added auto responder * update translation * update snapshot * updated snapshot * correction after review * removed unnecessary screen * refactored * updated the testIDs * Update Package.resolved * refactor * removed Mattermost as default server name * fix ts * refactored settings constant * display settings skeleton - pending: query for allowed themes * added 'allowedThemes' query * added section item * mention screen skeleton in place * added section and sectionItem component * added reply section to the mention screen * update i18n * rename screens properly * update i18n * Refactored to MentionSettings component * Refactored to ReplySettings component * style clean up * textTransform uppercase * rename Section/SectionItem to Block/BlockItem * added mobile push notif screen - push status section * adding text to those two components * correction following review * added mobile push notification section * added mobile push notification thread section * style fix * code fix * code fix * added skeleton for auto responder * code clean up * display theme skeleton * display theme skeleton * now using selected theme * clean up code * showing custom theme * setTheme implemented * code clean up * some corrections * Gekidou - Replace BlockItem with OptionItem (#6352) * Replaced BlockItem component with OptionItem * ui fix * corrections from PR review * code clean up * correction from PR review * fix - SettingsDisplay was wrongly removed from @screen/index * fix dependencies * corrections from peer review
This commit is contained in:
@@ -47,7 +47,7 @@ type SectionProps = {
|
||||
disableFooter?: boolean;
|
||||
disableHeader?: boolean;
|
||||
footerText?: SectionText;
|
||||
headerText: SectionText;
|
||||
headerText?: SectionText;
|
||||
containerStyles?: StyleProp<ViewStyle>;
|
||||
headerStyles?: StyleProp<TextStyle>;
|
||||
footerStyles?: StyleProp<TextStyle>;
|
||||
|
||||
@@ -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<ViewStyle>;
|
||||
description?: string | ReactElement;
|
||||
descriptionStyle?: StyleProp<TextStyle>;
|
||||
icon?: string;
|
||||
label: string | ReactElement;
|
||||
labelStyle?: StyleProp<TextStyle>;
|
||||
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 = (
|
||||
<CompassIcon
|
||||
name='check'
|
||||
style={selectStyle}
|
||||
testID={`${testID}.selected`}
|
||||
/>
|
||||
);
|
||||
} else if (actionType === ActionTypes.TOGGLE) {
|
||||
actionComponent = (
|
||||
<Switch
|
||||
onValueChange={action}
|
||||
value={selected}
|
||||
testID={`${testID}.toggled.${selected}`}
|
||||
/>
|
||||
);
|
||||
} else if (actionType === ActionTypes.ARROW) {
|
||||
actionComponent = (
|
||||
<CompassIcon
|
||||
name='chevron-right'
|
||||
style={style.arrow}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
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 = (
|
||||
<View
|
||||
testID={testID}
|
||||
style={[style.container, containerStyle]}
|
||||
>
|
||||
<View style={description ? style.doubleContainer : style.singleContainer}>
|
||||
<View style={style.labelContainer}>
|
||||
{Boolean(icon) && (
|
||||
<CompassIcon
|
||||
name={icon!}
|
||||
size={24}
|
||||
color={changeOpacity(theme.centerChannelColor, 0.6)}
|
||||
/>
|
||||
)}
|
||||
<Text
|
||||
style={labelStyles}
|
||||
testID={`${testID}.label`}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
</View>
|
||||
<Text
|
||||
style={[style.description, descriptionStyle]}
|
||||
testID={`${testID}.description`}
|
||||
>
|
||||
{description}
|
||||
</Text>
|
||||
</View>
|
||||
{actionComponent}
|
||||
</View>
|
||||
);
|
||||
|
||||
if (actionType === ActionTypes.DEFAULT || actionType === ActionTypes.SELECT || actionType === ActionTypes.ARROW) {
|
||||
return (
|
||||
<TouchableOpacity onPress={onPress}>
|
||||
{component}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
|
||||
return component;
|
||||
};
|
||||
|
||||
export default BlockItem;
|
||||
@@ -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<ViewStyle>;
|
||||
}
|
||||
|
||||
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 = (
|
||||
<View
|
||||
testID={testID}
|
||||
style={styles.container}
|
||||
style={[styles.container, containerStyle]}
|
||||
>
|
||||
<View style={styles.row}>
|
||||
<View style={styles.labelContainer}>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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({
|
||||
>
|
||||
<View>
|
||||
{showSelector && (
|
||||
<BlockItem
|
||||
<OptionItem
|
||||
testID='channel_info_form.make_private'
|
||||
label={makePrivateLabel}
|
||||
description={makePrivateDescription}
|
||||
action={handlePress}
|
||||
actionType={'toggle'}
|
||||
type={'toggle'}
|
||||
selected={isPrivate}
|
||||
icon={'lock-outline'}
|
||||
/>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 (
|
||||
<SafeAreaView
|
||||
edges={['left', 'right']}
|
||||
@@ -60,7 +71,7 @@ const Display = ({isTimezoneEnabled, isThemeSwitchingEnabled}: DisplayProps) =>
|
||||
{isThemeSwitchingEnabled && (
|
||||
<SettingOption
|
||||
optionName='theme'
|
||||
onPress={onPressHandler}
|
||||
onPress={goToThemeSettings}
|
||||
/>
|
||||
)}
|
||||
<SettingOption
|
||||
|
||||
@@ -6,7 +6,7 @@ import withObservables from '@nozbe/with-observables';
|
||||
import {combineLatest, of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
import {observeAllowedThemes, observeConfigBooleanValue} from '@queries/servers/system';
|
||||
import {observeAllowedThemesKeys, observeConfigBooleanValue} from '@queries/servers/system';
|
||||
import {WithDatabaseArgs} from '@typings/database/database';
|
||||
|
||||
import DisplaySettings from './display';
|
||||
@@ -15,9 +15,9 @@ const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
|
||||
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);
|
||||
}),
|
||||
|
||||
51
app/screens/settings/display_theme/custom_theme.tsx
Normal file
51
app/screens/settings/display_theme/custom_theme.tsx
Normal file
@@ -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 (
|
||||
<Block
|
||||
containerStyles={styles.containerStyles}
|
||||
disableHeader={true}
|
||||
>
|
||||
<OptionItem
|
||||
action={setTheme}
|
||||
type='select'
|
||||
value={customTheme.type}
|
||||
label={intl.formatMessage({id: 'user.settings.display.custom_theme', defaultMessage: 'Custom Theme'})}
|
||||
selected={theme.type?.toLowerCase() === customTheme.type?.toLowerCase()}
|
||||
/>
|
||||
</Block>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomTheme;
|
||||
80
app/screens/settings/display_theme/display_theme.tsx
Normal file
80
app/screens/settings/display_theme/display_theme.tsx
Normal file
@@ -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<Theme|null>();
|
||||
|
||||
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 (
|
||||
<ScrollView style={styles.container}>
|
||||
<View style={styles.wrapper}>
|
||||
<ThemeTiles
|
||||
allowedThemeKeys={allowedThemeKeys}
|
||||
onThemeChange={updateTheme}
|
||||
/>
|
||||
{customTheme && (
|
||||
<CustomTheme
|
||||
customTheme={customTheme}
|
||||
setTheme={updateTheme}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
export default DisplayTheme;
|
||||
27
app/screens/settings/display_theme/index.ts
Normal file
27
app/screens/settings/display_theme/index.ts
Normal file
@@ -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));
|
||||
275
app/screens/settings/display_theme/theme_thumbnail.tsx
Normal file
275
app/screens/settings/display_theme/theme_thumbnail.tsx
Normal file
@@ -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 (
|
||||
<Svg
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox={`-2 -2 ${baseWidth + 4} ${baseHeight + 4}`}
|
||||
fill='none'
|
||||
>
|
||||
<Rect
|
||||
fill={theme.centerChannelBg}
|
||||
x='0'
|
||||
y='0'
|
||||
width={baseWidth}
|
||||
height={baseHeight}
|
||||
/>
|
||||
<Rect
|
||||
fill={theme.newMessageSeparator}
|
||||
x={sidebarWidth}
|
||||
y={(spacing * 4) + (rowHeight * 3)}
|
||||
width={postsContainerWidth}
|
||||
height='1'
|
||||
/>
|
||||
<Rect
|
||||
fill={theme.buttonBg}
|
||||
x={sidebarWidth + (spacing * 4)}
|
||||
y={(spacing * 8) + (rowHeight * 6) + 1}
|
||||
width={buttonWidth}
|
||||
height={rowHeight}
|
||||
rx={rowRadius}
|
||||
/>
|
||||
<Rect
|
||||
fill={changeOpacity(theme.centerChannelColor, 0.16)}
|
||||
x={sidebarWidth + spacing}
|
||||
y={(spacing * 9) + (rowHeight * 7) + 1}
|
||||
width={postWidth}
|
||||
height={postInputHeight}
|
||||
rx={postInputHeight / 2}
|
||||
/>
|
||||
<Rect
|
||||
fill={theme.centerChannelBg}
|
||||
x={sidebarWidth + spacing + 1}
|
||||
y={(spacing * 9) + (rowHeight * 7) + 2}
|
||||
width={postWidth - 2}
|
||||
height={postInputHeight - 2}
|
||||
rx={(postInputHeight - 2) / 2}
|
||||
/>
|
||||
<G fill={changeOpacity(theme.centerChannelColor, 0.16)}>
|
||||
<Rect
|
||||
x={sidebarWidth + spacing}
|
||||
y={spacing}
|
||||
width={postWidth}
|
||||
height={rowHeight}
|
||||
rx={rowRadius}
|
||||
/>
|
||||
<Rect
|
||||
x={sidebarWidth + spacing}
|
||||
y={(spacing * 2) + rowHeight}
|
||||
width={postWidth}
|
||||
height={rowHeight}
|
||||
rx={rowRadius}
|
||||
/>
|
||||
<Rect
|
||||
x={sidebarWidth + spacing}
|
||||
y={(spacing * 3) + (rowHeight * 2)}
|
||||
width={postWidth}
|
||||
height={rowHeight}
|
||||
rx={rowRadius}
|
||||
/>
|
||||
<Rect
|
||||
x={sidebarWidth + spacing}
|
||||
y={(spacing * 5) + (rowHeight * 3) + 1}
|
||||
width={postWidth}
|
||||
height={rowHeight}
|
||||
rx={rowRadius}
|
||||
/>
|
||||
<Rect
|
||||
x={sidebarWidth + spacing}
|
||||
y={(spacing * 6) + (rowHeight * 4) + 1}
|
||||
width={postWidth}
|
||||
height={rowHeight}
|
||||
rx={rowRadius}
|
||||
/>
|
||||
<Rect
|
||||
x={sidebarWidth + spacing}
|
||||
y={(spacing * 7) + (rowHeight * 5) + 1}
|
||||
width={postWidth}
|
||||
height={rowHeight}
|
||||
rx={rowRadius}
|
||||
/>
|
||||
</G>
|
||||
<G>
|
||||
<Rect
|
||||
fill={theme.sidebarBg}
|
||||
x='0'
|
||||
y='0'
|
||||
width={sidebarWidth}
|
||||
height={baseHeight}
|
||||
/>
|
||||
<G fill={changeOpacity(theme.sidebarText, 0.48)}>
|
||||
<Circle
|
||||
cx={spacing + rowRadius}
|
||||
cy={spacing + rowRadius}
|
||||
r={rowRadius}
|
||||
/>
|
||||
<Circle
|
||||
cx={spacing + rowRadius}
|
||||
cy={(spacing * 2) + rowHeight + rowRadius}
|
||||
r={rowRadius}
|
||||
/>
|
||||
<Circle
|
||||
cx={spacing + rowRadius}
|
||||
cy={(spacing * 4) + (rowHeight * 3) + rowRadius}
|
||||
r={rowRadius}
|
||||
/>
|
||||
<Circle
|
||||
cx={spacing + rowRadius}
|
||||
cy={(spacing * 5) + (rowHeight * 4) + rowRadius}
|
||||
r={rowRadius}
|
||||
/>
|
||||
<Circle
|
||||
cx={spacing + rowRadius}
|
||||
cy={(spacing * 7) + (rowHeight * 6) + rowRadius}
|
||||
r={rowRadius}
|
||||
/>
|
||||
<Circle
|
||||
cx={spacing + rowRadius}
|
||||
cy={(spacing * 8) + (rowHeight * 7) + rowRadius}
|
||||
r={rowRadius}
|
||||
/>
|
||||
<Rect
|
||||
x={(spacing * 1.5) + rowHeight}
|
||||
y={spacing}
|
||||
width={channelNameWidth}
|
||||
height={rowHeight}
|
||||
rx={rowRadius}
|
||||
/>
|
||||
<Rect
|
||||
x={(spacing * 1.5) + rowHeight}
|
||||
y={(spacing * 2) + rowHeight}
|
||||
width={channelNameWidth}
|
||||
height={rowHeight}
|
||||
rx={rowRadius}
|
||||
/>
|
||||
<Rect
|
||||
x={(spacing * 1.5) + rowHeight}
|
||||
y={(spacing * 4) + (rowHeight * 3)}
|
||||
width={channelNameWidth}
|
||||
height={rowHeight}
|
||||
rx={rowRadius}
|
||||
/>
|
||||
<Rect
|
||||
x={(spacing * 1.5) + rowHeight}
|
||||
y={(spacing * 5) + (rowHeight * 4)}
|
||||
width={channelNameWidth}
|
||||
height={rowHeight}
|
||||
rx={rowRadius}
|
||||
/>
|
||||
<Rect
|
||||
x={(spacing * 1.5) + rowHeight}
|
||||
y={(spacing * 6) + (rowHeight * 5)}
|
||||
width={channelNameWidth}
|
||||
height={rowHeight}
|
||||
rx={rowRadius}
|
||||
/>
|
||||
<Rect
|
||||
x={(spacing * 1.5) + rowHeight}
|
||||
y={(spacing * 7) + (rowHeight * 6)}
|
||||
width={channelNameWidth}
|
||||
height={rowHeight}
|
||||
rx={rowRadius}
|
||||
/>
|
||||
<Rect
|
||||
x={(spacing * 1.5) + rowHeight}
|
||||
y={(spacing * 8) + (rowHeight * 7)}
|
||||
width={channelNameWidth}
|
||||
height={rowHeight}
|
||||
rx={rowRadius}
|
||||
/>
|
||||
<Rect
|
||||
x={(spacing * 1.5) + rowHeight}
|
||||
y={(spacing * 9) + (rowHeight * 8)}
|
||||
width={channelNameWidth}
|
||||
height={rowHeight}
|
||||
rx={rowRadius}
|
||||
/>
|
||||
</G>
|
||||
<Circle
|
||||
fill={theme.onlineIndicator}
|
||||
cx={spacing + rowRadius}
|
||||
cy={(spacing * 3) + (rowHeight * 2) + rowRadius}
|
||||
r={rowRadius}
|
||||
/>
|
||||
<Circle
|
||||
fill={theme.awayIndicator}
|
||||
cx={spacing + rowRadius}
|
||||
cy={(spacing * 6) + (rowHeight * 5) + rowRadius}
|
||||
r={rowRadius}
|
||||
/>
|
||||
<Circle
|
||||
fill={theme.dndIndicator}
|
||||
cx={spacing + rowRadius}
|
||||
cy={(spacing * 9) + (rowHeight * 8) + rowRadius}
|
||||
r={rowRadius}
|
||||
/>
|
||||
<G fill={theme.sidebarUnreadText}>
|
||||
<Circle
|
||||
cx={(spacing * 2.5) + rowHeight + channelNameWidth}
|
||||
cy={(spacing * 3) + (rowHeight * 2) + rowRadius}
|
||||
r={rowRadius}
|
||||
/>
|
||||
<Rect
|
||||
x={(spacing * 1.5) + rowHeight}
|
||||
y={(spacing * 3) + (rowHeight * 2)}
|
||||
width={channelNameWidth}
|
||||
height={rowHeight}
|
||||
rx={rowRadius}
|
||||
/>
|
||||
</G>
|
||||
</G>
|
||||
<Rect
|
||||
x='-1'
|
||||
y='-1'
|
||||
width={baseWidth + 2}
|
||||
height={baseHeight + 2}
|
||||
rx='4'
|
||||
stroke={borderColorBase}
|
||||
strokeWidth='2'
|
||||
/>
|
||||
<Rect
|
||||
x='-1'
|
||||
y='-1'
|
||||
width={baseWidth + 2}
|
||||
height={baseHeight + 2}
|
||||
rx='4'
|
||||
stroke={borderColorMix}
|
||||
strokeWidth='2'
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default ThemeThumbnail;
|
||||
149
app/screens/settings/display_theme/theme_tiles.tsx
Normal file
149
app/screens/settings/display_theme/theme_tiles.tsx
Normal file
@@ -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 (
|
||||
<TouchableOpacity
|
||||
onPress={onPressHandler}
|
||||
style={[style.container, layoutStyle.container]}
|
||||
>
|
||||
<View style={[style.imageWrapper, layoutStyle.thumbnail]}>
|
||||
<ThemeThumbnail
|
||||
borderColorBase={selected ? activeTheme.sidebarTextActiveBorder : activeTheme.centerChannelBg}
|
||||
borderColorMix={selected ? activeTheme.sidebarTextActiveBorder : changeOpacity(activeTheme.centerChannelColor, 0.16)}
|
||||
theme={theme}
|
||||
width={layoutStyle.thumbnail.width}
|
||||
/>
|
||||
{selected && (
|
||||
<CompassIcon
|
||||
name='check-circle'
|
||||
size={31.2}
|
||||
style={style.check}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
{label}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
type ThemeTilesProps = {
|
||||
allowedThemeKeys: string[];
|
||||
onThemeChange: (v: string) => void;
|
||||
}
|
||||
export const ThemeTiles = ({allowedThemeKeys, onThemeChange}: ThemeTilesProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const styles = getStyleSheet(theme);
|
||||
return (
|
||||
<View style={styles.tilesContainer}>
|
||||
{
|
||||
allowedThemeKeys.map((themeKey: string) => (
|
||||
<ThemeTile
|
||||
key={themeKey}
|
||||
label={(
|
||||
<Text style={styles.label}>
|
||||
{themeKey}
|
||||
</Text>
|
||||
)}
|
||||
action={onThemeChange}
|
||||
actionValue={themeKey}
|
||||
selected={theme.type?.toLowerCase() === themeKey.toLowerCase()}
|
||||
theme={Preferences.THEMES[themeKey]}
|
||||
activeTheme={theme}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -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
|
||||
<View
|
||||
style={styles.enabled}
|
||||
>
|
||||
<BlockItem
|
||||
label={
|
||||
<FormattedText
|
||||
id='notification_settings.auto_responder.enabled'
|
||||
defaultMessage='Enabled'
|
||||
style={styles.label}
|
||||
/>}
|
||||
<OptionItem
|
||||
label={intl.formatMessage({id: 'notification_settings.auto_responder.enabled', defaultMessage: 'Enabled'})}
|
||||
action={onAutoResponseToggle}
|
||||
actionType='toggle'
|
||||
type='toggle'
|
||||
selected={notifyProps.auto_responder_active === 'true'}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -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) && (
|
||||
<>
|
||||
<BlockItem
|
||||
<OptionItem
|
||||
action={toggleFirstNameMention}
|
||||
actionType='toggle'
|
||||
containerStyle={styles.container}
|
||||
description={intl.formatMessage({id: 'notification_settings.mentions.sensitiveName', defaultMessage: 'Your case sensitive first name'})}
|
||||
descriptionStyle={styles.desc}
|
||||
label={currentUser!.firstName}
|
||||
labelStyle={styles.label}
|
||||
selected={firstName}
|
||||
type='toggle'
|
||||
/>
|
||||
<View style={styles.separator}/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
{Boolean(currentUser?.username) && (
|
||||
<BlockItem
|
||||
<OptionItem
|
||||
action={toggleUsernameMention}
|
||||
actionType='toggle'
|
||||
containerStyle={styles.container}
|
||||
description={intl.formatMessage({id: 'notification_settings.mentions.sensitiveUsername', defaultMessage: 'Your non-case sensitive username'})}
|
||||
descriptionStyle={styles.desc}
|
||||
label={currentUser!.username}
|
||||
labelStyle={styles.label}
|
||||
selected={usernameMention}
|
||||
type='toggle'
|
||||
/>
|
||||
)}
|
||||
<View style={styles.separator}/>
|
||||
<BlockItem
|
||||
<OptionItem
|
||||
action={toggleChannelMentions}
|
||||
actionType='toggle'
|
||||
containerStyle={styles.container}
|
||||
description={intl.formatMessage({id: 'notification_settings.mentions.channelWide', defaultMessage: 'Channel-wide mentions'})}
|
||||
descriptionStyle={styles.desc}
|
||||
label='@channel, @all, @here'
|
||||
labelStyle={styles.label}
|
||||
selected={channel}
|
||||
type='toggle'
|
||||
/>
|
||||
<View style={styles.separator}/>
|
||||
<BlockItem
|
||||
<OptionItem
|
||||
action={goToNotificationSettingsMentionKeywords}
|
||||
actionType='arrow'
|
||||
containerStyle={styles.container}
|
||||
description={mentionKeys || intl.formatMessage({id: 'notification_settings.mentions.keywordsDescription', defaultMessage: 'Other words that trigger a mention'})}
|
||||
descriptionStyle={styles.desc}
|
||||
label={intl.formatMessage({id: 'notification_settings.mentions.keywords', defaultMessage: 'Keywords'})}
|
||||
labelStyle={styles.label}
|
||||
type='arrow'
|
||||
/>
|
||||
</Block>
|
||||
);
|
||||
|
||||
@@ -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}
|
||||
>
|
||||
<BlockItem
|
||||
<OptionItem
|
||||
action={setReplyNotifications}
|
||||
actionType='select'
|
||||
actionValue='any'
|
||||
type='select'
|
||||
value='any'
|
||||
containerStyle={styles.container}
|
||||
label={intl.formatMessage({id: 'notification_settings.threads_start_participate', defaultMessage: 'Threads that I start or participate in'})}
|
||||
labelStyle={styles.label}
|
||||
selected={replyNotificationType === 'any'}
|
||||
/>
|
||||
<View style={styles.separator}/>
|
||||
<BlockItem
|
||||
<OptionItem
|
||||
action={setReplyNotifications}
|
||||
actionType='select'
|
||||
actionValue='root'
|
||||
type='select'
|
||||
value='root'
|
||||
containerStyle={styles.container}
|
||||
label={intl.formatMessage({id: 'notification_settings.threads_start', defaultMessage: 'Threads that I start'})}
|
||||
labelStyle={styles.label}
|
||||
selected={replyNotificationType === 'root'}
|
||||
/>
|
||||
<View style={styles.separator}/>
|
||||
<BlockItem
|
||||
<OptionItem
|
||||
action={setReplyNotifications}
|
||||
actionType='select'
|
||||
actionValue='never'
|
||||
type='select'
|
||||
value='never'
|
||||
containerStyle={styles.container}
|
||||
label={intl.formatMessage({id: 'notification_settings.threads_mentions', defaultMessage: 'Mentions in threads'})}
|
||||
labelStyle={styles.label}
|
||||
selected={replyNotificationType === 'never'}
|
||||
/>
|
||||
</Block>
|
||||
|
||||
@@ -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 &&
|
||||
<>
|
||||
<BlockItem
|
||||
<OptionItem
|
||||
action={setMobilePushPref}
|
||||
actionType='select'
|
||||
actionValue='all'
|
||||
containerStyle={styles.container}
|
||||
label={intl.formatMessage({id: 'notification_settings.pushNotification.allActivity', defaultMessage: 'For all activity'})}
|
||||
labelStyle={styles.label}
|
||||
selected={pushStatus === 'all'}
|
||||
testID='notification_settings.pushNotification.allActivity'
|
||||
type='select'
|
||||
value='all'
|
||||
/>
|
||||
<View style={styles.separator}/>
|
||||
<BlockItem
|
||||
<OptionItem
|
||||
action={setMobilePushPref}
|
||||
actionType='select'
|
||||
actionValue='mention'
|
||||
containerStyle={styles.container}
|
||||
label={intl.formatMessage({id: 'notification_settings.pushNotification.onlyMentions', defaultMessage: 'Only for mentions and direct messages'})}
|
||||
labelStyle={styles.label}
|
||||
selected={pushStatus === 'mention'}
|
||||
testID='notification_settings.pushNotification.onlyMentions'
|
||||
type='select'
|
||||
value='mention'
|
||||
/>
|
||||
<View style={styles.separator}/>
|
||||
<BlockItem
|
||||
<OptionItem
|
||||
action={setMobilePushPref}
|
||||
actionType='select'
|
||||
actionValue='none'
|
||||
containerStyle={styles.container}
|
||||
label={intl.formatMessage({id: 'notification_settings.pushNotification.never', defaultMessage: 'Never'})}
|
||||
labelStyle={styles.label}
|
||||
selected={pushStatus === 'none'}
|
||||
testID='notification_settings.pushNotification.never'
|
||||
type='select'
|
||||
value='none'
|
||||
/>
|
||||
</>
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
>
|
||||
<BlockItem
|
||||
<OptionItem
|
||||
action={setMobilePushStatus}
|
||||
containerStyle={styles.container}
|
||||
label={intl.formatMessage({id: 'notification_settings.mobile.online', defaultMessage: 'Online, away or offline'})}
|
||||
labelStyle={styles.label}
|
||||
action={setMobilePushStatus}
|
||||
actionType='select'
|
||||
actionValue='online'
|
||||
selected={pushStatus === 'online'}
|
||||
type='select'
|
||||
value='online'
|
||||
/>
|
||||
<View style={styles.separator}/>
|
||||
<BlockItem
|
||||
<OptionItem
|
||||
action={setMobilePushStatus}
|
||||
containerStyle={styles.container}
|
||||
label={intl.formatMessage({id: 'notification_settings.mobile.away', defaultMessage: 'Away or offline'})}
|
||||
labelStyle={styles.label}
|
||||
action={setMobilePushStatus}
|
||||
actionType='select'
|
||||
actionValue='away'
|
||||
selected={pushStatus === 'away'}
|
||||
type='select'
|
||||
value='away'
|
||||
/>
|
||||
<View style={styles.separator}/>
|
||||
<BlockItem
|
||||
label={intl.formatMessage({id: 'notification_settings.mobile.offline', defaultMessage: 'Offline'})}
|
||||
labelStyle={styles.label}
|
||||
<OptionItem
|
||||
action={setMobilePushStatus}
|
||||
actionType='select'
|
||||
actionValue='offline'
|
||||
containerStyle={styles.container}
|
||||
label={intl.formatMessage({id: 'notification_settings.mobile.offline', defaultMessage: 'Offline'})}
|
||||
selected={pushStatus === 'offline'}
|
||||
type='select'
|
||||
value='offline'
|
||||
/>
|
||||
</Block>
|
||||
);
|
||||
|
||||
@@ -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}
|
||||
>
|
||||
<BlockItem
|
||||
label={(
|
||||
<FormattedText
|
||||
id='notification_settings.push_threads.description'
|
||||
defaultMessage={'Notify me about all replies to threads I\'m following'}
|
||||
style={styles.label}
|
||||
/>
|
||||
)}
|
||||
<OptionItem
|
||||
action={onMobilePushThreadChanged}
|
||||
actionType='toggle'
|
||||
label={intl.formatMessage({id: 'notification_settings.push_threads.description', defaultMessage: 'Notify me about all replies to threads I\'m following'})}
|
||||
selected={pushThread === 'all'}
|
||||
type='toggle'
|
||||
/>
|
||||
<View style={styles.separator}/>
|
||||
</Block>
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user