MM-45221 - Gekidou Settings fixes - part 1 (#6472)
* modifying setting option * navigates to email screen * UI construction [in progress] * hooking up withObservables * email settings - need to save now * adding a bit of paddings * setting initial value * Update notification_email.tsx * UI Polish - main setting screen * UI Polish - Mention * UI Polish - Notification main screen * code clean up * code clean up * UI Polish Notification * UI Polish * code clean up * UI Polish - OOO * fix observable for email interval * fix ooo * fix ooo * added setting_row_label component * further clean up * UI Polish - Display - [ IN PROGRESS ] * UI Polish - Display - [ IN PROGRESS ] * UI Polish - Timezone Select [ IN PROGRESS ] * Update index.test.tsx.snap * Update app/screens/settings/notification_email/notification_email.tsx Co-authored-by: Daniel Espino García <larkox@gmail.com> * refactor after review * update option_item so that action can accept type React.Dispatch<React.SetStateAction Co-authored-by: Daniel Espino García <larkox@gmail.com>
This commit is contained in:
parent
0f0c7d5795
commit
431406f09b
47 changed files with 1220 additions and 1001 deletions
|
|
@ -20,13 +20,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
fontSize: 13,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
},
|
||||
items: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
borderTopWidth: 1,
|
||||
borderBottomWidth: 1,
|
||||
borderTopColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
borderBottomColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
},
|
||||
footer: {
|
||||
marginTop: 10,
|
||||
marginHorizontal: 15,
|
||||
|
|
@ -36,13 +29,13 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
};
|
||||
});
|
||||
|
||||
type SectionText = {
|
||||
export type SectionText = {
|
||||
id: string;
|
||||
defaultMessage: string;
|
||||
values?: MessageDescriptor;
|
||||
}
|
||||
|
||||
type SectionProps = {
|
||||
export type BlockProps = {
|
||||
children: React.ReactNode;
|
||||
disableFooter?: boolean;
|
||||
disableHeader?: boolean;
|
||||
|
|
@ -62,7 +55,7 @@ const Block = ({
|
|||
headerStyles,
|
||||
headerText,
|
||||
footerStyles,
|
||||
}: SectionProps) => {
|
||||
}: BlockProps) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
|
|
@ -76,7 +69,7 @@ const Block = ({
|
|||
style={[styles.header, headerStyles]}
|
||||
/>
|
||||
}
|
||||
<View style={[styles.items, containerStyles]}>
|
||||
<View style={containerStyles}>
|
||||
{children}
|
||||
</View>
|
||||
{(footerText && !disableFooter) &&
|
||||
|
|
|
|||
|
|
@ -52,14 +52,13 @@ const BaseOption = ({
|
|||
|
||||
return (
|
||||
<MenuItem
|
||||
testID={testID}
|
||||
labelComponent={label}
|
||||
iconContainerStyle={styles.iconContainerStyle}
|
||||
iconName={iconName}
|
||||
isDestructor={isDestructive}
|
||||
labelComponent={label}
|
||||
onPress={onPress}
|
||||
separator={false}
|
||||
theme={theme}
|
||||
isDestructor={isDestructive}
|
||||
testID={testID}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -110,10 +110,13 @@ exports[`DrawerItem should match snapshot 1`] = `
|
|||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "rgba(63,67,80,0.2)",
|
||||
"height": 1,
|
||||
}
|
||||
Array [
|
||||
Object {
|
||||
"backgroundColor": "rgba(63,67,80,0.2)",
|
||||
"height": 1,
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {Platform, StyleProp, TextStyle, View, ViewStyle} from 'react-native';
|
|||
import CompassIcon from '@components/compass_icon';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
export const ITEM_HEIGHT = 50;
|
||||
|
|
@ -64,6 +65,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
|
||||
export type MenuItemProps = {
|
||||
centered?: boolean;
|
||||
chevronStyle?: StyleProp<ViewStyle>;
|
||||
containerStyle?: StyleProp<ViewStyle>;
|
||||
defaultMessage?: string;
|
||||
i18nId?: string;
|
||||
|
|
@ -78,24 +80,34 @@ export type MenuItemProps = {
|
|||
onPress: () => void;
|
||||
rightComponent?: ReactNode;
|
||||
separator?: boolean;
|
||||
separatorStyle?: StyleProp<ViewStyle>;
|
||||
showArrow?: boolean;
|
||||
testID: string;
|
||||
theme: Theme;
|
||||
};
|
||||
const MenuItem = (props: MenuItemProps) => {
|
||||
const {
|
||||
centered, containerStyle, defaultMessage = '', i18nId, iconContainerStyle, iconName, isDestructor = false,
|
||||
isLink = false, labelComponent, labelStyle, leftComponent, messageValues, onPress, rightComponent,
|
||||
separator = true, showArrow = false, testID, theme,
|
||||
} = props;
|
||||
|
||||
const MenuItem = ({
|
||||
centered,
|
||||
chevronStyle,
|
||||
containerStyle,
|
||||
defaultMessage = '',
|
||||
i18nId,
|
||||
iconContainerStyle,
|
||||
iconName,
|
||||
isDestructor = false,
|
||||
isLink = false,
|
||||
labelComponent,
|
||||
labelStyle,
|
||||
leftComponent,
|
||||
messageValues,
|
||||
onPress,
|
||||
rightComponent,
|
||||
separator = true,
|
||||
separatorStyle,
|
||||
showArrow = false,
|
||||
testID,
|
||||
}: MenuItemProps) => {
|
||||
const theme = useTheme();
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
const destructor: any = {};
|
||||
if (isDestructor) {
|
||||
destructor.color = theme.errorTextColor;
|
||||
}
|
||||
|
||||
let icon;
|
||||
if (leftComponent) {
|
||||
icon = leftComponent;
|
||||
|
|
@ -103,7 +115,7 @@ const MenuItem = (props: MenuItemProps) => {
|
|||
icon = (
|
||||
<CompassIcon
|
||||
name={iconName}
|
||||
style={[style.icon, destructor]}
|
||||
style={[style.icon, isDestructor && {color: theme.errorTextColor}]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -119,7 +131,7 @@ const MenuItem = (props: MenuItemProps) => {
|
|||
style={[
|
||||
style.label,
|
||||
labelStyle,
|
||||
destructor,
|
||||
isDestructor && {color: theme.errorTextColor},
|
||||
centered ? style.centerLabel : {},
|
||||
isLink && style.linkContainer,
|
||||
]}
|
||||
|
|
@ -144,15 +156,15 @@ const MenuItem = (props: MenuItemProps) => {
|
|||
<View style={style.labelContainer}>
|
||||
{label}
|
||||
</View>
|
||||
{rightComponent}
|
||||
{Boolean(showArrow) && (
|
||||
<CompassIcon
|
||||
name='chevron-right'
|
||||
style={style.chevron}
|
||||
style={[style.chevron, chevronStyle]}
|
||||
/>
|
||||
)}
|
||||
{rightComponent}
|
||||
</View>
|
||||
{Boolean(separator) && (<View style={style.divider}/>)}
|
||||
{Boolean(separator) && (<View style={[style.divider, separatorStyle]}/>)}
|
||||
</View>
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,15 +2,15 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useMemo} from 'react';
|
||||
import {Platform, StyleProp, Switch, Text, TouchableOpacity, View, ViewStyle} from 'react-native';
|
||||
import {Platform, 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';
|
||||
|
||||
type Props = {
|
||||
action?: (value: string | boolean) => void;
|
||||
export type OptionItemProps = {
|
||||
action?: (React.Dispatch<React.SetStateAction<string | boolean>>)|((value: string | boolean) => void) ;
|
||||
description?: string;
|
||||
inline?: boolean;
|
||||
destructive?: boolean;
|
||||
|
|
@ -22,6 +22,8 @@ type Props = {
|
|||
type: OptionType;
|
||||
value?: string;
|
||||
containerStyle?: StyleProp<ViewStyle>;
|
||||
optionLabelTextStyle?: StyleProp<TextStyle>;
|
||||
optionDescriptionTextStyle?: StyleProp<TextStyle>;
|
||||
}
|
||||
|
||||
const OptionType = {
|
||||
|
|
@ -95,10 +97,21 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
|||
});
|
||||
|
||||
const OptionItem = ({
|
||||
action, description, destructive, icon,
|
||||
info, inline = false, label, selected,
|
||||
testID = 'optionItem', type, value, containerStyle,
|
||||
}: Props) => {
|
||||
action,
|
||||
containerStyle,
|
||||
description,
|
||||
destructive,
|
||||
icon,
|
||||
info,
|
||||
inline = false,
|
||||
label,
|
||||
optionDescriptionTextStyle,
|
||||
optionLabelTextStyle,
|
||||
selected,
|
||||
testID = 'optionItem',
|
||||
type,
|
||||
value,
|
||||
}: OptionItemProps) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
|
|
@ -181,14 +194,14 @@ const OptionItem = ({
|
|||
)}
|
||||
<View style={labelStyle}>
|
||||
<Text
|
||||
style={labelTextStyle}
|
||||
style={[optionLabelTextStyle, labelTextStyle]}
|
||||
testID={`${testID}.label`}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
{Boolean(description) &&
|
||||
<Text
|
||||
style={descriptionTextStyle}
|
||||
style={[optionDescriptionTextStyle, descriptionTextStyle]}
|
||||
testID={`${testID}.description`}
|
||||
>
|
||||
{description}
|
||||
|
|
@ -199,10 +212,11 @@ const OptionItem = ({
|
|||
</View>
|
||||
{Boolean(actionComponent || info) &&
|
||||
<View style={styles.actionContainer}>
|
||||
{Boolean(info) &&
|
||||
<View style={styles.infoContainer}>
|
||||
<Text style={styles.info}>{info}</Text>
|
||||
</View>
|
||||
{
|
||||
Boolean(info) &&
|
||||
<View style={styles.infoContainer}>
|
||||
<Text style={[styles.info, destructive && {color: theme.dndIndicator}]}>{info}</Text>
|
||||
</View>
|
||||
}
|
||||
{actionComponent}
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ export const SETTINGS_DISPLAY_TIMEZONE = 'SettingsDisplayTimezone';
|
|||
export const SETTINGS_DISPLAY_TIMEZONE_SELECT = 'SettingsDisplayTimezoneSelect';
|
||||
export const SETTINGS_NOTIFICATION = 'SettingsNotification';
|
||||
export const SETTINGS_NOTIFICATION_AUTO_RESPONDER = 'SettingsNotificationAutoResponder';
|
||||
export const SETTINGS_NOTIFICATION_EMAIL = 'SettingsNotificationEmail';
|
||||
export const SETTINGS_NOTIFICATION_MENTION = 'SettingsNotificationMention';
|
||||
export const SETTINGS_NOTIFICATION_PUSH = 'SettingsNotificationPush';
|
||||
export const SNACK_BAR = 'SnackBar';
|
||||
|
|
@ -108,6 +109,7 @@ export default {
|
|||
SETTINGS_DISPLAY_TIMEZONE_SELECT,
|
||||
SETTINGS_NOTIFICATION,
|
||||
SETTINGS_NOTIFICATION_AUTO_RESPONDER,
|
||||
SETTINGS_NOTIFICATION_EMAIL,
|
||||
SETTINGS_NOTIFICATION_MENTION,
|
||||
SETTINGS_NOTIFICATION_PUSH,
|
||||
SNACK_BAR,
|
||||
|
|
|
|||
|
|
@ -88,7 +88,6 @@ const CustomStatus = ({isCustomStatusExpirySupported, isTablet, currentUser}: Cu
|
|||
/>}
|
||||
separator={false}
|
||||
onPress={goToCustomStatusScreen}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -89,7 +89,6 @@ const AccountOptions = ({user, enableCustomUserStatuses, isCustomStatusExpirySup
|
|||
<Settings
|
||||
isTablet={isTablet}
|
||||
style={styles.menuLabel}
|
||||
theme={theme}
|
||||
/>
|
||||
<View style={styles.divider}/>
|
||||
<Logout
|
||||
|
|
|
|||
|
|
@ -50,7 +50,8 @@ const Settings = ({style, theme}: Props) => {
|
|||
|
||||
return (
|
||||
<MenuItem
|
||||
testID='account.logout.action'
|
||||
iconName='exit-to-app'
|
||||
isDestructor={true}
|
||||
labelComponent={(
|
||||
<View>
|
||||
<FormattedText
|
||||
|
|
@ -66,11 +67,9 @@ const Settings = ({style, theme}: Props) => {
|
|||
/>
|
||||
</View>
|
||||
)}
|
||||
iconName='exit-to-app'
|
||||
isDestructor={true}
|
||||
onPress={onLogout}
|
||||
separator={false}
|
||||
theme={theme}
|
||||
testID='account.logout.action'
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -15,10 +15,9 @@ import {preventDoubleTap} from '@utils/tap';
|
|||
type Props = {
|
||||
isTablet: boolean;
|
||||
style: TextStyle;
|
||||
theme: Theme;
|
||||
}
|
||||
|
||||
const Settings = ({isTablet, style, theme}: Props) => {
|
||||
const Settings = ({isTablet, style}: Props) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const openSettings = useCallback(preventDoubleTap(() => {
|
||||
|
|
@ -34,7 +33,7 @@ const Settings = ({isTablet, style, theme}: Props) => {
|
|||
|
||||
return (
|
||||
<MenuItem
|
||||
testID='account.settings.action'
|
||||
iconName='settings-outline'
|
||||
labelComponent={
|
||||
<FormattedText
|
||||
id='account.settings'
|
||||
|
|
@ -42,10 +41,9 @@ const Settings = ({isTablet, style, theme}: Props) => {
|
|||
style={style}
|
||||
/>
|
||||
}
|
||||
iconName='settings-outline'
|
||||
onPress={openSettings}
|
||||
separator={false}
|
||||
theme={theme}
|
||||
testID='account.settings.action'
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -120,7 +120,6 @@ const UserStatus = ({currentUser, style, theme}: Props) => {
|
|||
|
||||
return (
|
||||
<MenuItem
|
||||
testID='account.status.action'
|
||||
labelComponent={
|
||||
<StatusLabel
|
||||
labelStyle={style}
|
||||
|
|
@ -133,9 +132,9 @@ const UserStatus = ({currentUser, style, theme}: Props) => {
|
|||
status={currentUser.status}
|
||||
/>
|
||||
}
|
||||
separator={false}
|
||||
onPress={handleSetStatus}
|
||||
theme={theme}
|
||||
separator={false}
|
||||
testID='account.status.action'
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ const YourProfile = ({isTablet, style, theme}: Props) => {
|
|||
|
||||
return (
|
||||
<MenuItem
|
||||
testID='account.your_profile.action'
|
||||
iconName={ACCOUNT_OUTLINE_IMAGE}
|
||||
labelComponent={
|
||||
<FormattedText
|
||||
id='account.your_profile'
|
||||
|
|
@ -41,10 +41,9 @@ const YourProfile = ({isTablet, style, theme}: Props) => {
|
|||
style={style}
|
||||
/>
|
||||
}
|
||||
iconName={ACCOUNT_OUTLINE_IMAGE}
|
||||
onPress={openProfile}
|
||||
separator={false}
|
||||
theme={theme}
|
||||
testID='account.your_profile.action'
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -37,8 +37,6 @@ const ShowMoreButton = ({onPress, showMore}: ShowMoreButtonProps) => {
|
|||
|
||||
return (
|
||||
<MenuItem
|
||||
testID={'mobile.search.show_more'}
|
||||
onPress={onPress}
|
||||
labelComponent={
|
||||
<FormattedText
|
||||
id={id}
|
||||
|
|
@ -46,8 +44,9 @@ const ShowMoreButton = ({onPress, showMore}: ShowMoreButtonProps) => {
|
|||
style={style.showMore}
|
||||
/>
|
||||
}
|
||||
onPress={onPress}
|
||||
separator={false}
|
||||
theme={theme}
|
||||
testID={'mobile.search.show_more'}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -175,6 +175,9 @@ Navigation.setLazyComponentRegistrator((screenName) => {
|
|||
case Screens.SETTINGS_NOTIFICATION_AUTO_RESPONDER:
|
||||
screen = withServerDatabase(require('@screens/settings/notification_auto_responder').default);
|
||||
break;
|
||||
case Screens.SETTINGS_NOTIFICATION_EMAIL:
|
||||
screen = withServerDatabase(require('@screens/settings/notification_email').default);
|
||||
break;
|
||||
case Screens.SETTINGS_NOTIFICATION_MENTION:
|
||||
screen = withServerDatabase(require('@screens/settings/notification_mention').default);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -3,40 +3,31 @@
|
|||
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {View, TouchableOpacity} from 'react-native';
|
||||
import {Edge, SafeAreaView} from 'react-native-safe-area-context';
|
||||
import {TouchableOpacity} from 'react-native';
|
||||
|
||||
import OptionItem from '@components/option_item';
|
||||
import {useTheme} from '@context/theme';
|
||||
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
|
||||
import {popTopScreen} from '@screens/navigation';
|
||||
import {deleteFileCache, getAllFilesInCachesDirectory, getFormattedFileSize} from '@utils/file';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
import SettingContainer from '../setting_container';
|
||||
import SettingOption from '../setting_option';
|
||||
|
||||
import type {ReadDirItem} from 'react-native-fs';
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
screen: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
body: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.06),
|
||||
flex: 1,
|
||||
paddingTop: 35,
|
||||
},
|
||||
itemStyle: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
paddingHorizontal: 8,
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const EMPTY_FILES: ReadDirItem[] = [];
|
||||
const EMPTY_SERVERS: string[] = [];
|
||||
const EDGES: Edge[] = ['left', 'right'];
|
||||
|
||||
type AdvancedSettingsProps = {
|
||||
componentId: string;
|
||||
|
|
@ -81,29 +72,22 @@ const AdvancedSettings = ({componentId}: AdvancedSettingsProps) => {
|
|||
const disabled = Boolean(dataSize && (dataSize > 0));
|
||||
|
||||
return (
|
||||
<SafeAreaView
|
||||
edges={EDGES}
|
||||
style={styles.screen}
|
||||
testID='settings_display.screen'
|
||||
>
|
||||
<View
|
||||
style={styles.body}
|
||||
<SettingContainer>
|
||||
<TouchableOpacity
|
||||
onPress={onPressDeleteData}
|
||||
disabled={disabled}
|
||||
activeOpacity={disabled ? 0 : 1}
|
||||
>
|
||||
<TouchableOpacity
|
||||
onPress={onPressDeleteData}
|
||||
disabled={disabled}
|
||||
activeOpacity={disabled ? 0 : 1}
|
||||
>
|
||||
<OptionItem
|
||||
containerStyle={styles.itemStyle}
|
||||
destructive={true}
|
||||
label={intl.formatMessage({id: 'advanced_settings.delete_data', defaultMessage: 'Delete Documents & Data'})}
|
||||
info={getFormattedFileSize(dataSize || 0)}
|
||||
type='none'
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
<SettingOption
|
||||
containerStyle={styles.itemStyle}
|
||||
destructive={true}
|
||||
icon='trash-can-outline'
|
||||
info={getFormattedFileSize(dataSize || 0)}
|
||||
label={intl.formatMessage({id: 'advanced_settings.delete_data', defaultMessage: 'Delete Documents & Data'})}
|
||||
type='none'
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</SettingContainer>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,19 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import {t} from '@i18n';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
import type {IntlShape} from 'react-intl';
|
||||
|
||||
export const getSaveButton = (buttonId: string, intl: IntlShape, color: string) => ({
|
||||
color,
|
||||
enabled: false,
|
||||
id: buttonId,
|
||||
showAsAction: 'always' as const,
|
||||
testID: 'notification_settings.mentions.save.button',
|
||||
text: intl.formatMessage({id: 'settings.save', defaultMessage: 'Save'}),
|
||||
...typography('Body', 100, 'SemiBold'),
|
||||
});
|
||||
|
||||
export const SettingOptionConfig = {
|
||||
notification: {
|
||||
|
|
@ -47,8 +60,14 @@ export const NotificationsOptionConfig = {
|
|||
iconName: 'cellphone',
|
||||
testID: 'notification_settings.push_notification',
|
||||
},
|
||||
email: {
|
||||
defaultMessage: 'Email',
|
||||
i18nId: t('notification_settings.email'),
|
||||
iconName: 'email-outline',
|
||||
testID: 'notification_settings.email',
|
||||
},
|
||||
automatic_dm_replies: {
|
||||
defaultMessage: 'Automatic Direct Message Replies',
|
||||
defaultMessage: 'Automatic replies',
|
||||
i18nId: t('notification_settings.ooo_auto_responder'),
|
||||
iconName: 'reply-outline',
|
||||
testID: 'notification_settings.automatic_dm_replies',
|
||||
|
|
@ -1,49 +1,54 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import React, {useMemo} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Platform, ScrollView, View} from 'react-native';
|
||||
import {SafeAreaView} from 'react-native-safe-area-context';
|
||||
|
||||
import {Screens} from '@constants';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {t} from '@i18n';
|
||||
import {goToScreen} from '@screens/navigation';
|
||||
import SettingOption from '@screens/settings/setting_option';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {getUserTimezoneProps} from '@utils/user';
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
wrapper: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.06),
|
||||
...Platform.select({
|
||||
ios: {
|
||||
flex: 1,
|
||||
paddingTop: 35,
|
||||
},
|
||||
}),
|
||||
},
|
||||
divider: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
height: 1,
|
||||
width: '100%',
|
||||
},
|
||||
};
|
||||
});
|
||||
import SettingContainer from '../setting_container';
|
||||
import SettingItem from '../setting_item';
|
||||
import SettingRowLabel from '../setting_row_label';
|
||||
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
const TIME_FORMAT = [
|
||||
{
|
||||
id: t('display_settings.clock.standard'),
|
||||
defaultMessage: '12-hour',
|
||||
},
|
||||
{
|
||||
id: t('display_settings.clock.military'),
|
||||
defaultMessage: '24-hour',
|
||||
},
|
||||
];
|
||||
|
||||
const TIMEZONE_FORMAT = [
|
||||
{
|
||||
id: t('display_settings.tz.auto'),
|
||||
defaultMessage: 'Auto',
|
||||
},
|
||||
{
|
||||
id: t('display_settings.tz.manual'),
|
||||
defaultMessage: 'Manual',
|
||||
},
|
||||
];
|
||||
|
||||
type DisplayProps = {
|
||||
isTimezoneEnabled: boolean;
|
||||
currentUser: UserModel;
|
||||
hasMilitaryTimeFormat: boolean;
|
||||
isThemeSwitchingEnabled: boolean;
|
||||
isTimezoneEnabled: boolean;
|
||||
}
|
||||
const Display = ({isTimezoneEnabled, isThemeSwitchingEnabled}: DisplayProps) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
const Display = ({currentUser, hasMilitaryTimeFormat, isThemeSwitchingEnabled, isTimezoneEnabled}: DisplayProps) => {
|
||||
const intl = useIntl();
|
||||
const theme = useTheme();
|
||||
const timezone = useMemo(() => getUserTimezoneProps(currentUser), [currentUser.timezone]);
|
||||
|
||||
const goToThemeSettings = preventDoubleTap(() => {
|
||||
const screen = Screens.SETTINGS_DISPLAY_THEME;
|
||||
|
|
@ -66,35 +71,39 @@ const Display = ({isTimezoneEnabled, isThemeSwitchingEnabled}: DisplayProps) =>
|
|||
});
|
||||
|
||||
return (
|
||||
<SafeAreaView
|
||||
edges={['left', 'right']}
|
||||
testID='notification_display.screen'
|
||||
style={styles.container}
|
||||
>
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.wrapper}
|
||||
alwaysBounceVertical={false}
|
||||
>
|
||||
<View style={styles.divider}/>
|
||||
{isThemeSwitchingEnabled && (
|
||||
<SettingOption
|
||||
optionName='theme'
|
||||
onPress={goToThemeSettings}
|
||||
/>
|
||||
)}
|
||||
<SettingOption
|
||||
optionName='clock'
|
||||
onPress={goToClockDisplaySettings}
|
||||
<SettingContainer>
|
||||
{isThemeSwitchingEnabled && (
|
||||
<SettingItem
|
||||
optionName='theme'
|
||||
onPress={goToThemeSettings}
|
||||
rightComponent={
|
||||
<SettingRowLabel
|
||||
text={theme.type || ''}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{isTimezoneEnabled && (
|
||||
<SettingOption
|
||||
optionName='timezone'
|
||||
onPress={goToTimezoneSettings}
|
||||
)}
|
||||
<SettingItem
|
||||
optionName='clock'
|
||||
onPress={goToClockDisplaySettings}
|
||||
rightComponent={
|
||||
<SettingRowLabel
|
||||
text={intl.formatMessage(hasMilitaryTimeFormat ? TIME_FORMAT[1] : TIME_FORMAT[0])}
|
||||
/>
|
||||
)}
|
||||
<View style={styles.divider}/>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
}
|
||||
/>
|
||||
{isTimezoneEnabled && (
|
||||
<SettingItem
|
||||
optionName='timezone'
|
||||
onPress={goToTimezoneSettings}
|
||||
rightComponent={
|
||||
<SettingRowLabel
|
||||
text={intl.formatMessage(timezone.useAutomaticTimezone ? TIMEZONE_FORMAT[0] : TIMEZONE_FORMAT[1])}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</SettingContainer>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,11 @@ import withObservables from '@nozbe/with-observables';
|
|||
import {combineLatest, of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
import {Preferences} from '@constants';
|
||||
import {getPreferenceAsBool} from '@helpers/api/preference';
|
||||
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
|
||||
import {observeAllowedThemesKeys, observeConfigBooleanValue} from '@queries/servers/system';
|
||||
import {observeCurrentUser} from '@queries/servers/user';
|
||||
|
||||
import DisplaySettings from './display';
|
||||
|
||||
|
|
@ -27,6 +31,13 @@ const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
|
|||
return {
|
||||
isTimezoneEnabled,
|
||||
isThemeSwitchingEnabled,
|
||||
hasMilitaryTimeFormat: queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS).
|
||||
observeWithColumns(['value']).pipe(
|
||||
switchMap(
|
||||
(preferences) => of$(getPreferenceAsBool(preferences, Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time', false)),
|
||||
),
|
||||
),
|
||||
currentUser: observeCurrentUser(database),
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -3,12 +3,8 @@
|
|||
|
||||
import React, {useCallback, useEffect, useMemo, useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {View} from 'react-native';
|
||||
import {Edge, SafeAreaView} from 'react-native-safe-area-context';
|
||||
|
||||
import {savePreference} from '@actions/remote/preference';
|
||||
import Block from '@components/block';
|
||||
import OptionItem from '@components/option_item';
|
||||
import {Preferences} from '@constants';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
|
|
@ -16,40 +12,23 @@ import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
|
|||
import useNavButtonPressed from '@hooks/navigation_button_pressed';
|
||||
import {t} from '@i18n';
|
||||
import {popTopScreen, setButtons} from '@screens/navigation';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
import {getSaveButton} from '../config';
|
||||
import SettingBlock from '../setting_block';
|
||||
import SettingContainer from '../setting_container';
|
||||
import SettingOption from '../setting_option';
|
||||
import SettingSeparator from '../settings_separator';
|
||||
|
||||
const footer = {
|
||||
id: t('settings_display.clock.preferTime'),
|
||||
defaultMessage: 'Select how you prefer time displayed.',
|
||||
};
|
||||
|
||||
const edges: Edge[] = ['left', 'right'];
|
||||
const CLOCK_TYPE = {
|
||||
NORMAL: 'NORMAL',
|
||||
MILITARY: 'MILITARY',
|
||||
} as const;
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
wrapper: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.06),
|
||||
flex: 1,
|
||||
paddingTop: 35,
|
||||
},
|
||||
divider: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
height: 1,
|
||||
},
|
||||
containerStyle: {
|
||||
paddingHorizontal: 8,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const SAVE_CLOCK_BUTTON_ID = 'settings_display.clock.save.button';
|
||||
|
||||
type DisplayClockProps = {
|
||||
|
|
@ -63,18 +42,7 @@ const DisplayClock = ({componentId, currentUserId, hasMilitaryTimeFormat}: Displ
|
|||
const serverUrl = useServerUrl();
|
||||
const intl = useIntl();
|
||||
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const saveButton = useMemo(() => {
|
||||
return {
|
||||
id: SAVE_CLOCK_BUTTON_ID,
|
||||
enabled: false,
|
||||
showAsAction: 'always' as const,
|
||||
testID: 'settings_display.save.button',
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
text: intl.formatMessage({id: 'settings.display.militaryClock.save', defaultMessage: 'Save'}),
|
||||
};
|
||||
}, [theme.sidebarHeaderTextColor]);
|
||||
const saveButton = useMemo(() => getSaveButton(SAVE_CLOCK_BUTTON_ID, intl, theme.sidebarHeaderTextColor), [theme.sidebarHeaderTextColor]);
|
||||
|
||||
const onSelectClockPreference = useCallback((clockType: keyof typeof CLOCK_TYPE) => {
|
||||
setIsMilitaryTimeFormat(clockType === CLOCK_TYPE.MILITARY);
|
||||
|
|
@ -108,38 +76,30 @@ const DisplayClock = ({componentId, currentUserId, hasMilitaryTimeFormat}: Displ
|
|||
useNavButtonPressed(SAVE_CLOCK_BUTTON_ID, componentId, saveClockDisplayPreference, [isMilitaryTimeFormat]);
|
||||
|
||||
return (
|
||||
<SafeAreaView
|
||||
edges={edges}
|
||||
style={styles.container}
|
||||
testID='settings_display.screen'
|
||||
>
|
||||
<View style={styles.wrapper}>
|
||||
<Block
|
||||
disableHeader={true}
|
||||
footerText={footer}
|
||||
>
|
||||
<OptionItem
|
||||
action={onSelectClockPreference}
|
||||
containerStyle={styles.containerStyle}
|
||||
label={intl.formatMessage({id: 'settings_display.clock.normal', defaultMessage: '12-hour clock (example: 4:00 PM)'})}
|
||||
selected={!isMilitaryTimeFormat}
|
||||
testID='clock_display_settings.normal_clock.action'
|
||||
type='select'
|
||||
value={CLOCK_TYPE.NORMAL}
|
||||
/>
|
||||
<View style={styles.divider}/>
|
||||
<OptionItem
|
||||
action={onSelectClockPreference}
|
||||
containerStyle={styles.containerStyle}
|
||||
label={intl.formatMessage({id: 'settings_display.clock.military', defaultMessage: '24-hour clock (example: 16:00)'})}
|
||||
selected={isMilitaryTimeFormat}
|
||||
testID='clock_display_settings.military_clock.action'
|
||||
type='select'
|
||||
value={CLOCK_TYPE.MILITARY}
|
||||
/>
|
||||
</Block>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
<SettingContainer>
|
||||
<SettingBlock
|
||||
disableHeader={true}
|
||||
footerText={footer}
|
||||
>
|
||||
<SettingOption
|
||||
action={onSelectClockPreference}
|
||||
label={intl.formatMessage({id: 'settings_display.clock.normal', defaultMessage: '12-hour clock (example: 4:00 PM)'})}
|
||||
selected={!isMilitaryTimeFormat}
|
||||
testID='clock_display_settings.normal_clock.action'
|
||||
type='select'
|
||||
value={CLOCK_TYPE.NORMAL}
|
||||
/>
|
||||
<SettingSeparator/>
|
||||
<SettingOption
|
||||
action={onSelectClockPreference}
|
||||
label={intl.formatMessage({id: 'settings_display.clock.military', defaultMessage: '24-hour clock (example: 16:00)'})}
|
||||
selected={isMilitaryTimeFormat}
|
||||
testID='clock_display_settings.military_clock.action'
|
||||
type='select'
|
||||
value={CLOCK_TYPE.MILITARY}
|
||||
/>
|
||||
</SettingBlock>
|
||||
</SettingContainer>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -3,23 +3,17 @@
|
|||
|
||||
import React from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {StyleSheet} from 'react-native';
|
||||
|
||||
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,
|
||||
},
|
||||
};
|
||||
import SettingBlock from '../setting_block';
|
||||
import SettingOption from '../setting_option';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
containerStyles: {
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
});
|
||||
|
||||
type CustomThemeProps = {
|
||||
|
|
@ -28,23 +22,21 @@ type CustomThemeProps = {
|
|||
}
|
||||
|
||||
const CustomTheme = ({customTheme, setTheme}: CustomThemeProps) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
const intl = useIntl();
|
||||
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Block
|
||||
<SettingBlock
|
||||
containerStyles={styles.containerStyles}
|
||||
disableHeader={true}
|
||||
>
|
||||
<OptionItem
|
||||
<SettingOption
|
||||
action={setTheme}
|
||||
type='select'
|
||||
value={customTheme.type}
|
||||
label={intl.formatMessage({id: 'settings_display.custom_theme', defaultMessage: 'Custom Theme'})}
|
||||
selected={theme.type?.toLowerCase() === customTheme.type?.toLowerCase()}
|
||||
/>
|
||||
</Block>
|
||||
</SettingBlock>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -2,30 +2,17 @@
|
|||
// 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 SettingContainer from '../setting_container';
|
||||
|
||||
import CustomTheme from './custom_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;
|
||||
|
|
@ -37,8 +24,6 @@ const DisplayTheme = ({allowedThemeKeys, currentTeamId, currentUserId}: DisplayT
|
|||
const theme = useTheme();
|
||||
const [customTheme, setCustomTheme] = useState<Theme|null>();
|
||||
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
useEffect(() => {
|
||||
if (theme.type === 'custom') {
|
||||
setCustomTheme(theme);
|
||||
|
|
@ -60,20 +45,18 @@ const DisplayTheme = ({allowedThemeKeys, currentTeamId, currentUserId}: DisplayT
|
|||
}, [serverUrl, allowedThemeKeys, currentTeamId]);
|
||||
|
||||
return (
|
||||
<ScrollView style={styles.container}>
|
||||
<View style={styles.wrapper}>
|
||||
<ThemeTiles
|
||||
allowedThemeKeys={allowedThemeKeys}
|
||||
onThemeChange={updateTheme}
|
||||
<SettingContainer>
|
||||
<ThemeTiles
|
||||
allowedThemeKeys={allowedThemeKeys}
|
||||
onThemeChange={updateTheme}
|
||||
/>
|
||||
{customTheme && (
|
||||
<CustomTheme
|
||||
customTheme={customTheme}
|
||||
setTheme={updateTheme}
|
||||
/>
|
||||
{customTheme && (
|
||||
<CustomTheme
|
||||
customTheme={customTheme}
|
||||
setTheme={updateTheme}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</ScrollView>
|
||||
)}
|
||||
</SettingContainer>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ export const ThemeTile = ({
|
|||
theme,
|
||||
}: ThemeTileProps) => {
|
||||
const isTablet = useIsTablet();
|
||||
const style = getStyleSheet(activeTheme);
|
||||
const styles = getStyleSheet(activeTheme);
|
||||
const {width: deviceWidth} = useWindowDimensions();
|
||||
|
||||
const layoutStyle = useMemo(() => {
|
||||
|
|
@ -95,9 +95,9 @@ export const ThemeTile = ({
|
|||
return (
|
||||
<TouchableOpacity
|
||||
onPress={onPressHandler}
|
||||
style={[style.container, layoutStyle.container]}
|
||||
style={[styles.container, layoutStyle.container]}
|
||||
>
|
||||
<View style={[style.imageWrapper, layoutStyle.thumbnail]}>
|
||||
<View style={[styles.imageWrapper, layoutStyle.thumbnail]}>
|
||||
<ThemeThumbnail
|
||||
borderColorBase={selected ? activeTheme.sidebarTextActiveBorder : activeTheme.centerChannelBg}
|
||||
borderColorMix={selected ? activeTheme.sidebarTextActiveBorder : changeOpacity(activeTheme.centerChannelColor, 0.16)}
|
||||
|
|
@ -108,7 +108,7 @@ export const ThemeTile = ({
|
|||
<CompassIcon
|
||||
name='check-circle'
|
||||
size={31.2}
|
||||
style={style.check}
|
||||
style={styles.check}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -4,10 +4,8 @@
|
|||
import React, {useCallback, useEffect, useMemo, useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {View} from 'react-native';
|
||||
import {Edge, SafeAreaView} from 'react-native-safe-area-context';
|
||||
|
||||
import {updateMe} from '@actions/remote/user';
|
||||
import OptionItem from '@components/option_item';
|
||||
import {Screens} from '@constants';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
|
|
@ -15,38 +13,16 @@ import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
|
|||
import useNavButtonPressed from '@hooks/navigation_button_pressed';
|
||||
import {goToScreen, popTopScreen, setButtons} from '@screens/navigation';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {getDeviceTimezone} from '@utils/timezone';
|
||||
import {getTimezoneRegion, getUserTimezoneProps} from '@utils/user';
|
||||
|
||||
import {getSaveButton} from '../config';
|
||||
import SettingContainer from '../setting_container';
|
||||
import SettingOption from '../setting_option';
|
||||
import SettingSeparator from '../settings_separator';
|
||||
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
wrapper: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.06),
|
||||
flex: 1,
|
||||
paddingTop: 35,
|
||||
},
|
||||
divider: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
height: 1,
|
||||
},
|
||||
separator: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
height: 1,
|
||||
marginLeft: 15,
|
||||
},
|
||||
content: {
|
||||
paddingHorizontal: 8,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const edges: Edge[] = ['left', 'right'];
|
||||
const SAVE_TIMEZONE_BUTTON_ID = 'save_timezone';
|
||||
|
||||
type DisplayTimezoneProps = {
|
||||
|
|
@ -59,8 +35,6 @@ const DisplayTimezone = ({currentUser, componentId}: DisplayTimezoneProps) => {
|
|||
const timezone = useMemo(() => getUserTimezoneProps(currentUser), [currentUser.timezone]);
|
||||
const [userTimezone, setUserTimezone] = useState(timezone);
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const updateAutomaticTimezone = (useAutomaticTimezone: boolean) => {
|
||||
const automaticTimezone = getDeviceTimezone();
|
||||
setUserTimezone((prev) => ({
|
||||
|
|
@ -102,22 +76,18 @@ const DisplayTimezone = ({currentUser, componentId}: DisplayTimezoneProps) => {
|
|||
close();
|
||||
}, [userTimezone, currentUser.timezone, serverUrl]);
|
||||
|
||||
const saveButton = useMemo(() => {
|
||||
return {
|
||||
id: SAVE_TIMEZONE_BUTTON_ID,
|
||||
enabled: false,
|
||||
showAsAction: 'always' as const,
|
||||
testID: 'notification_settings.auto_res.save.button',
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
text: intl.formatMessage({id: 'settings.save', defaultMessage: 'Save'}),
|
||||
};
|
||||
}, [theme.sidebarHeaderTextColor]);
|
||||
const saveButton = useMemo(() => getSaveButton(SAVE_TIMEZONE_BUTTON_ID, intl, theme.sidebarHeaderTextColor), [theme.sidebarHeaderTextColor]);
|
||||
|
||||
useEffect(() => {
|
||||
const enabled =
|
||||
timezone.useAutomaticTimezone !== userTimezone.useAutomaticTimezone ||
|
||||
timezone.automaticTimezone !== userTimezone.automaticTimezone ||
|
||||
timezone.manualTimezone !== userTimezone.manualTimezone;
|
||||
|
||||
const buttons = {
|
||||
rightButtons: [{
|
||||
...saveButton,
|
||||
enabled: timezone.useAutomaticTimezone !== userTimezone.useAutomaticTimezone,
|
||||
enabled,
|
||||
}],
|
||||
};
|
||||
setButtons(componentId, buttons);
|
||||
|
|
@ -128,35 +98,28 @@ const DisplayTimezone = ({currentUser, componentId}: DisplayTimezoneProps) => {
|
|||
useAndroidHardwareBackHandler(componentId, close);
|
||||
|
||||
return (
|
||||
<SafeAreaView
|
||||
edges={edges}
|
||||
style={styles.container}
|
||||
>
|
||||
<View style={styles.wrapper}>
|
||||
<View style={styles.divider}/>
|
||||
<OptionItem
|
||||
action={updateAutomaticTimezone}
|
||||
containerStyle={styles.content}
|
||||
description={getTimezoneRegion(userTimezone.automaticTimezone)}
|
||||
label={intl.formatMessage({id: 'settings_display.timezone.automatically', defaultMessage: 'Set automatically'})}
|
||||
selected={userTimezone.useAutomaticTimezone}
|
||||
type='toggle'
|
||||
/>
|
||||
{!userTimezone.useAutomaticTimezone && (
|
||||
<View>
|
||||
<View style={styles.separator}/>
|
||||
<OptionItem
|
||||
action={goToSelectTimezone}
|
||||
containerStyle={styles.content}
|
||||
description={getTimezoneRegion(userTimezone.manualTimezone)}
|
||||
label={intl.formatMessage({id: 'settings_display.timezone.manual', defaultMessage: 'Change timezone'})}
|
||||
type='arrow'
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
<View style={styles.divider}/>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
<SettingContainer>
|
||||
<SettingSeparator/>
|
||||
<SettingOption
|
||||
action={updateAutomaticTimezone}
|
||||
description={getTimezoneRegion(userTimezone.automaticTimezone)}
|
||||
label={intl.formatMessage({id: 'settings_display.timezone.automatically', defaultMessage: 'Set automatically'})}
|
||||
selected={userTimezone.useAutomaticTimezone}
|
||||
type='toggle'
|
||||
/>
|
||||
{!userTimezone.useAutomaticTimezone && (
|
||||
<View>
|
||||
<SettingSeparator/>
|
||||
<SettingOption
|
||||
action={goToSelectTimezone}
|
||||
description={getTimezoneRegion(userTimezone.manualTimezone)}
|
||||
label={intl.formatMessage({id: 'settings_display.timezone.manual', defaultMessage: 'Change timezone'})}
|
||||
type='arrow'
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
<SettingSeparator/>
|
||||
</SettingContainer>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useEffect, useState} from 'react';
|
||||
import React, {useCallback, useEffect, useMemo, useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {FlatList, View} from 'react-native';
|
||||
import {Edge, SafeAreaView} from 'react-native-safe-area-context';
|
||||
|
|
@ -31,6 +31,13 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
searchBar: {
|
||||
height: 38,
|
||||
marginVertical: 5,
|
||||
marginBottom: 32,
|
||||
},
|
||||
inputContainerStyle: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.08),
|
||||
paddingHorizontal: 12,
|
||||
marginLeft: 12,
|
||||
marginTop: 12,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
@ -57,6 +64,16 @@ const SelectTimezones = ({selectedTimezone, onBack}: SelectTimezonesProps) => {
|
|||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const cancelButtonProps = useMemo(() => ({
|
||||
buttonTextStyle: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.64),
|
||||
...typography('Body', 100),
|
||||
},
|
||||
buttonStyle: {
|
||||
marginTop: 12,
|
||||
},
|
||||
}), [theme.centerChannelColor]);
|
||||
|
||||
const [timezones, setTimezones] = useState<string[]>(EMPTY_TIMEZONES);
|
||||
const [initialScrollIndex, setInitialScrollIndex] = useState<number>(0);
|
||||
const [value, setValue] = useState('');
|
||||
|
|
@ -113,11 +130,12 @@ const SelectTimezones = ({selectedTimezone, onBack}: SelectTimezonesProps) => {
|
|||
<View style={styles.searchBar}>
|
||||
<Search
|
||||
autoCapitalize='none'
|
||||
containerStyle={styles.searchBarContainer}
|
||||
cancelButtonProps={cancelButtonProps}
|
||||
inputContainerStyle={styles.inputContainerStyle}
|
||||
inputStyle={styles.searchBarInput}
|
||||
keyboardAppearance={getKeyboardAppearanceFromTheme(theme)}
|
||||
onChangeText={setValue}
|
||||
placeholder={intl.formatMessage({id: 'search_bar.search', defaultMessage: 'Search'})}
|
||||
placeholder={intl.formatMessage({id: 'search_bar.search.placeholder', defaultMessage: 'Search timezone'})}
|
||||
placeholderTextColor={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
selectionColor={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||
testID='settings.select_timezone.search_bar'
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import React, {useCallback} from 'react';
|
||||
import {TouchableOpacity, View, Text} from 'react-native';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {useTheme} from '@context/theme';
|
||||
import SettingSeparator from '@screens/settings/settings_separator';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
|
|
@ -14,10 +15,9 @@ const ITEM_HEIGHT = 45;
|
|||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
itemContainer: {
|
||||
flexDirection: 'row',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
paddingHorizontal: 15,
|
||||
paddingHorizontal: 18,
|
||||
height: ITEM_HEIGHT,
|
||||
},
|
||||
item: {
|
||||
|
|
@ -27,7 +27,13 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
},
|
||||
itemText: {
|
||||
color: theme.centerChannelColor,
|
||||
...typography('Body', 100, 'Regular'),
|
||||
...typography('Body', 200, 'Regular'),
|
||||
},
|
||||
body: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
lineStyles: {
|
||||
width: '100%',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
@ -40,28 +46,35 @@ const TimezoneRow = ({onPressTimezone, selectedTimezone, timezone}: TimezoneRowP
|
|||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const onTimezoneSelect = () => {
|
||||
const onTimezoneSelect = useCallback(() => {
|
||||
onPressTimezone(timezone);
|
||||
};
|
||||
}, [onPressTimezone, timezone]);
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={styles.itemContainer}
|
||||
key={timezone}
|
||||
onPress={onTimezoneSelect}
|
||||
style={styles.itemContainer}
|
||||
>
|
||||
<View style={styles.item}>
|
||||
<Text style={styles.itemText}>
|
||||
{timezone}
|
||||
</Text>
|
||||
<View
|
||||
style={styles.body}
|
||||
>
|
||||
<View style={styles.item}>
|
||||
<Text style={styles.itemText}>
|
||||
{timezone}
|
||||
</Text>
|
||||
</View>
|
||||
{timezone === selectedTimezone && (
|
||||
<CompassIcon
|
||||
color={theme.linkColor}
|
||||
name='check'
|
||||
size={24}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
{timezone === selectedTimezone && (
|
||||
<CompassIcon
|
||||
name='check'
|
||||
size={24}
|
||||
color={theme.linkColor}
|
||||
/>
|
||||
)}
|
||||
<SettingSeparator
|
||||
lineStyles={styles.lineStyles}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,13 +3,10 @@
|
|||
|
||||
import React, {useCallback, useEffect, useMemo, useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {View} from 'react-native';
|
||||
import {Edge, SafeAreaView} from 'react-native-safe-area-context';
|
||||
|
||||
import {updateMe} from '@actions/remote/user';
|
||||
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 {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
|
|
@ -21,11 +18,16 @@ import {changeOpacity, makeStyleSheetFromTheme, getKeyboardAppearanceFromTheme}
|
|||
import {typography} from '@utils/typography';
|
||||
import {getNotificationProps} from '@utils/user';
|
||||
|
||||
import {getSaveButton} from '../config';
|
||||
import SettingContainer from '../setting_container';
|
||||
import SettingOption from '../setting_option';
|
||||
import SettingSeparator from '../settings_separator';
|
||||
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
const headerText = {
|
||||
id: t('notification_settings.auto_responder'),
|
||||
defaultMessage: 'Custom message',
|
||||
const label = {
|
||||
id: t('notification_settings.auto_responder.message'),
|
||||
defaultMessage: 'Message',
|
||||
};
|
||||
|
||||
const OOO = {
|
||||
|
|
@ -34,43 +36,25 @@ const OOO = {
|
|||
};
|
||||
const SAVE_OOO_BUTTON_ID = 'notification_settings.auto_responder.save.button';
|
||||
|
||||
const edges: Edge[] = ['left', 'right'];
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
return {
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
wrapper: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.06),
|
||||
flex: 1,
|
||||
paddingTop: 35,
|
||||
},
|
||||
input: {
|
||||
color: theme.centerChannelColor,
|
||||
height: 150,
|
||||
paddingHorizontal: 15,
|
||||
paddingVertical: 10,
|
||||
...typography('Body', 200, 'Regular'),
|
||||
flex: 1,
|
||||
},
|
||||
textInputContainer: {
|
||||
width: '91%',
|
||||
marginTop: 20,
|
||||
alignSelf: 'center',
|
||||
height: 154,
|
||||
},
|
||||
footer: {
|
||||
paddingHorizontal: 15,
|
||||
paddingHorizontal: 20,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.5),
|
||||
textAlign: 'justify',
|
||||
...typography('Body', 75),
|
||||
},
|
||||
area: {
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
label: {
|
||||
color: theme.centerChannelColor,
|
||||
...typography('Body', 100, 'Regular'),
|
||||
},
|
||||
enabled: {
|
||||
paddingHorizontal: 8,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
marginBottom: 16,
|
||||
...typography('Body', 75, 'Regular'),
|
||||
marginTop: 20,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
@ -83,53 +67,33 @@ const NotificationAutoResponder = ({currentUser, componentId}: NotificationAutoR
|
|||
const theme = useTheme();
|
||||
const serverUrl = useServerUrl();
|
||||
const intl = useIntl();
|
||||
const userNotifyProps = useMemo(() => getNotificationProps(currentUser), [currentUser.notifyProps]);
|
||||
const [autoResponderActive, setAutoResponderActive] = useState((currentUser.status === General.OUT_OF_OFFICE && userNotifyProps.auto_responder_active) ? 'true' : 'false');
|
||||
const [autoResponderMessage, setAutoResponderMessage] = useState(userNotifyProps.auto_responder_message || intl.formatMessage(OOO));
|
||||
const notifyProps = useMemo(() => getNotificationProps(currentUser), []); // dependency array should remain empty
|
||||
|
||||
const initialAutoResponderActive = useMemo(() => Boolean(currentUser.status === General.OUT_OF_OFFICE && notifyProps.auto_responder_active === 'true'), []); // dependency array should remain empty
|
||||
const [autoResponderActive, setAutoResponderActive] = useState<boolean>(initialAutoResponderActive);
|
||||
|
||||
const initialOOOMsg = useMemo(() => notifyProps.auto_responder_message || intl.formatMessage(OOO), []); // dependency array should remain empty
|
||||
const [autoResponderMessage, setAutoResponderMessage] = useState<string>(initialOOOMsg);
|
||||
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const close = () => popTopScreen(componentId);
|
||||
|
||||
const saveButton = useMemo(() => {
|
||||
return {
|
||||
id: SAVE_OOO_BUTTON_ID,
|
||||
enabled: false,
|
||||
showAsAction: 'always' as const,
|
||||
testID: 'notification_settings.auto_res.save.button',
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
text: intl.formatMessage({id: 'settings.save', defaultMessage: 'Save'}),
|
||||
};
|
||||
}, [theme.sidebarHeaderTextColor]);
|
||||
|
||||
const onAutoResponseToggle = useCallback((active: boolean) => {
|
||||
setAutoResponderActive(`${active}`);
|
||||
}, []);
|
||||
|
||||
const onAutoResponseChangeText = useCallback((message: string) => {
|
||||
setAutoResponderMessage(message);
|
||||
}, []);
|
||||
const saveButton = useMemo(() => getSaveButton(SAVE_OOO_BUTTON_ID, intl, theme.sidebarHeaderTextColor), [theme.sidebarHeaderTextColor]);
|
||||
|
||||
const saveAutoResponder = useCallback(() => {
|
||||
const notifyProps = {
|
||||
...userNotifyProps,
|
||||
auto_responder_active: autoResponderActive,
|
||||
auto_responder_message: autoResponderMessage,
|
||||
} as unknown as UserNotifyProps;
|
||||
|
||||
updateMe(serverUrl, {
|
||||
notify_props: notifyProps,
|
||||
notify_props: {
|
||||
...notifyProps,
|
||||
auto_responder_active: `${autoResponderActive}`,
|
||||
auto_responder_message: autoResponderMessage,
|
||||
},
|
||||
});
|
||||
close();
|
||||
}, [serverUrl, autoResponderActive, autoResponderMessage, userNotifyProps]);
|
||||
}, [serverUrl, autoResponderActive, autoResponderMessage, notifyProps]);
|
||||
|
||||
useEffect(() => {
|
||||
const updatedMsg = userNotifyProps?.auto_responder_message !== autoResponderMessage;
|
||||
const enabling = currentUser.status !== General.OUT_OF_OFFICE && autoResponderActive === 'true';
|
||||
const disabling = currentUser.status === General.OUT_OF_OFFICE && autoResponderActive === 'false';
|
||||
|
||||
const enabled = enabling || disabling || updatedMsg;
|
||||
|
||||
const enabled = initialAutoResponderActive !== autoResponderActive || initialOOOMsg !== autoResponderMessage;
|
||||
const buttons = {
|
||||
rightButtons: [{
|
||||
...saveButton,
|
||||
|
|
@ -137,55 +101,48 @@ const NotificationAutoResponder = ({currentUser, componentId}: NotificationAutoR
|
|||
}],
|
||||
};
|
||||
setButtons(componentId, buttons);
|
||||
}, [autoResponderActive, autoResponderMessage, componentId, currentUser.status, userNotifyProps.auto_responder_message]);
|
||||
}, [autoResponderActive, autoResponderMessage, componentId, currentUser.status, notifyProps.auto_responder_message]);
|
||||
|
||||
useNavButtonPressed(SAVE_OOO_BUTTON_ID, componentId, saveAutoResponder, [saveAutoResponder]);
|
||||
|
||||
useAndroidHardwareBackHandler(componentId, close);
|
||||
|
||||
return (
|
||||
<SafeAreaView
|
||||
edges={edges}
|
||||
style={styles.container}
|
||||
>
|
||||
<View style={styles.wrapper}>
|
||||
<View
|
||||
style={styles.enabled}
|
||||
>
|
||||
<OptionItem
|
||||
label={intl.formatMessage({id: 'notification_settings.auto_responder.enabled', defaultMessage: 'Enabled'})}
|
||||
action={onAutoResponseToggle}
|
||||
type='toggle'
|
||||
selected={autoResponderActive === 'true'}
|
||||
/>
|
||||
</View>
|
||||
{autoResponderActive === 'true' && (
|
||||
<FloatingTextInput
|
||||
allowFontScaling={true}
|
||||
autoCapitalize='none'
|
||||
autoCorrect={false}
|
||||
blurOnSubmit={true}
|
||||
keyboardAppearance={getKeyboardAppearanceFromTheme(theme)}
|
||||
label={intl.formatMessage(headerText)}
|
||||
multiline={true}
|
||||
onChangeText={onAutoResponseChangeText}
|
||||
placeholder={intl.formatMessage(headerText)}
|
||||
placeholderTextColor={changeOpacity(theme.centerChannelColor, 0.4)}
|
||||
returnKeyType='done'
|
||||
textInputStyle={styles.input}
|
||||
textAlignVertical='top'
|
||||
theme={theme}
|
||||
underlineColorAndroid='transparent'
|
||||
value={autoResponderMessage || ''}
|
||||
/>
|
||||
)}
|
||||
<FormattedText
|
||||
id={'notification_settings.auto_responder.footer_message'}
|
||||
defaultMessage={'Set a custom message that will be automatically sent in response to Direct Messages. Mentions in Public and Private Channels will not trigger the automated reply. Enabling Automatic Replies sets your status to Out of Office and disables email and push notifications.'}
|
||||
style={styles.footer}
|
||||
<SettingContainer>
|
||||
<SettingOption
|
||||
label={intl.formatMessage({id: 'notification_settings.auto_responder.to.enable', defaultMessage: 'Enable automatic replies'})}
|
||||
action={setAutoResponderActive}
|
||||
type='toggle'
|
||||
selected={autoResponderActive}
|
||||
/>
|
||||
<SettingSeparator/>
|
||||
{autoResponderActive && (
|
||||
<FloatingTextInput
|
||||
allowFontScaling={true}
|
||||
autoCapitalize='none'
|
||||
autoCorrect={false}
|
||||
blurOnSubmit={true}
|
||||
containerStyle={styles.textInputContainer}
|
||||
keyboardAppearance={getKeyboardAppearanceFromTheme(theme)}
|
||||
label={intl.formatMessage(label)}
|
||||
multiline={true}
|
||||
onChangeText={setAutoResponderMessage}
|
||||
placeholder={intl.formatMessage(label)}
|
||||
placeholderTextColor={changeOpacity(theme.centerChannelColor, 0.4)}
|
||||
returnKeyType='done'
|
||||
textAlignVertical='top'
|
||||
textInputStyle={styles.input}
|
||||
theme={theme}
|
||||
underlineColorAndroid='transparent'
|
||||
value={autoResponderMessage || ''}
|
||||
/>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
)}
|
||||
<FormattedText
|
||||
id={'notification_settings.auto_responder.footer.message'}
|
||||
defaultMessage={'Set a custom message that is automatically sent in response to direct messages, such as an out of office or vacation reply. Enabling this setting changes your status to Out of Office and disables notifications.'}
|
||||
style={styles.footer}
|
||||
/>
|
||||
</SettingContainer>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
33
app/screens/settings/notification_email/index.tsx
Normal file
33
app/screens/settings/notification_email/index.tsx
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
// 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 {of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
import {Preferences} from '@constants';
|
||||
import {getPreferenceValue} from '@helpers/api/preference';
|
||||
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
|
||||
import {observeConfigBooleanValue} from '@queries/servers/system';
|
||||
import {observeIsCRTEnabled} from '@queries/servers/thread';
|
||||
import {observeCurrentUser} from '@queries/servers/user';
|
||||
|
||||
import NotificationEmail from './notification_email';
|
||||
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
|
||||
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
|
||||
return {
|
||||
currentUser: observeCurrentUser(database),
|
||||
enableEmailBatching: observeConfigBooleanValue(database, 'EnableEmailBatching'),
|
||||
emailInterval: queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_NOTIFICATIONS).
|
||||
observeWithColumns(['value']).pipe(
|
||||
switchMap((preferences) => of$(getPreferenceValue(preferences, Preferences.CATEGORY_NOTIFICATIONS, Preferences.EMAIL_INTERVAL, Preferences.INTERVAL_NOT_SET))),
|
||||
),
|
||||
isCRTEnabled: observeIsCRTEnabled(database),
|
||||
sendEmailNotifications: observeConfigBooleanValue(database, 'SendEmailNotifications'),
|
||||
};
|
||||
});
|
||||
|
||||
export default withDatabase(enhanced(NotificationEmail));
|
||||
203
app/screens/settings/notification_email/notification_email.tsx
Normal file
203
app/screens/settings/notification_email/notification_email.tsx
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useEffect, useMemo, useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Text} from 'react-native';
|
||||
|
||||
import {savePreference} from '@actions/remote/preference';
|
||||
import {updateMe} from '@actions/remote/user';
|
||||
import {Preferences} from '@constants';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
|
||||
import useNavButtonPressed from '@hooks/navigation_button_pressed';
|
||||
import {t} from '@i18n';
|
||||
import {popTopScreen, setButtons} from '@screens/navigation';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
import {getEmailInterval, getNotificationProps} from '@utils/user';
|
||||
|
||||
import {getSaveButton} from '../config';
|
||||
import SettingBlock from '../setting_block';
|
||||
import SettingContainer from '../setting_container';
|
||||
import SettingOption from '../setting_option';
|
||||
import SettingSeparator from '../settings_separator';
|
||||
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
disabled: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.64),
|
||||
...typography('Body', 75, 'Regular'),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const emailHeaderText = {
|
||||
id: t('notification_settings.email.send'),
|
||||
defaultMessage: 'Send email notifications',
|
||||
};
|
||||
const emailFooterText = {
|
||||
id: t('notification_settings.email.emailInfo'),
|
||||
defaultMessage: 'Email notifications are sent for mentions and direct messages when you are offline or away for more than 5 minutes.',
|
||||
};
|
||||
|
||||
const emailHeaderCRTText = {
|
||||
id: t('notification_settings.email.crt.send'),
|
||||
defaultMessage: 'Thread reply notifications',
|
||||
};
|
||||
const emailFooterCRTText = {
|
||||
id: t('notification_settings.email.crt.emailInfo'),
|
||||
defaultMessage: "When enabled, any reply to a thread you're following will send an email notification",
|
||||
};
|
||||
|
||||
const SAVE_EMAIL_BUTTON_ID = 'settings_notification.email.save.button';
|
||||
|
||||
type NotificationEmailProps = {
|
||||
componentId: string;
|
||||
currentUser: UserModel;
|
||||
emailInterval: string;
|
||||
enableEmailBatching: boolean;
|
||||
isCRTEnabled: boolean;
|
||||
sendEmailNotifications: boolean;
|
||||
}
|
||||
|
||||
const NotificationEmail = ({componentId, currentUser, emailInterval, enableEmailBatching, isCRTEnabled, sendEmailNotifications}: NotificationEmailProps) => {
|
||||
const notifyProps = useMemo(() => getNotificationProps(currentUser), [currentUser.notifyProps]);
|
||||
const initialInterval = useMemo(() => getEmailInterval(
|
||||
sendEmailNotifications && notifyProps?.email === 'true',
|
||||
enableEmailBatching,
|
||||
parseInt(emailInterval, 10),
|
||||
).toString(), []); // dependency array should remain empty
|
||||
const initialEmailThreads = useMemo(() => Boolean(notifyProps?.email_threads === 'all'), []); // dependency array should remain empty
|
||||
|
||||
const [notifyInterval, setNotifyInterval] = useState<string>(initialInterval);
|
||||
const [emailThreads, setEmailThreads] = useState(initialEmailThreads);
|
||||
|
||||
const intl = useIntl();
|
||||
const serverUrl = useServerUrl();
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const saveButton = useMemo(() => getSaveButton(SAVE_EMAIL_BUTTON_ID, intl, theme.sidebarHeaderTextColor), [theme.sidebarHeaderTextColor]);
|
||||
|
||||
const close = () => popTopScreen(componentId);
|
||||
|
||||
const saveEmail = useCallback(async () => {
|
||||
const promises = [];
|
||||
const updatePromise = updateMe(serverUrl, {
|
||||
notify_props: {
|
||||
...notifyProps,
|
||||
email: `${sendEmailNotifications && notifyInterval !== Preferences.INTERVAL_NEVER.toString()}`,
|
||||
email_threads: emailThreads ? 'all' : 'mention',
|
||||
},
|
||||
});
|
||||
promises.push(updatePromise);
|
||||
|
||||
if (notifyInterval !== initialInterval) {
|
||||
const emailIntervalPreference = {
|
||||
category: Preferences.CATEGORY_NOTIFICATIONS,
|
||||
name: Preferences.EMAIL_INTERVAL,
|
||||
user_id: currentUser.id,
|
||||
value: notifyInterval,
|
||||
};
|
||||
const savePrefPromise = savePreference(serverUrl, [emailIntervalPreference]);
|
||||
promises.push(savePrefPromise);
|
||||
}
|
||||
await Promise.all(promises);
|
||||
close();
|
||||
}, [notifyProps, notifyInterval, emailThreads, serverUrl, currentUser.id, sendEmailNotifications]);
|
||||
|
||||
useEffect(() => {
|
||||
const buttons = {
|
||||
rightButtons: [{
|
||||
...saveButton,
|
||||
enabled: notifyInterval !== initialInterval || emailThreads !== initialEmailThreads,
|
||||
}],
|
||||
};
|
||||
setButtons(componentId, buttons);
|
||||
}, [componentId, saveButton, notifyInterval, emailThreads]);
|
||||
|
||||
useAndroidHardwareBackHandler(componentId, close);
|
||||
useNavButtonPressed(SAVE_EMAIL_BUTTON_ID, componentId, saveEmail, [saveEmail]);
|
||||
|
||||
return (
|
||||
<SettingContainer>
|
||||
<SettingBlock
|
||||
disableFooter={!sendEmailNotifications}
|
||||
footerText={emailFooterText}
|
||||
headerText={emailHeaderText}
|
||||
>
|
||||
{sendEmailNotifications &&
|
||||
<>
|
||||
<SettingOption
|
||||
action={setNotifyInterval}
|
||||
label={intl.formatMessage({id: 'notification_settings.email.immediately', defaultMessage: 'Immediately'})}
|
||||
selected={notifyInterval === `${Preferences.INTERVAL_IMMEDIATE}`}
|
||||
testID='notification_settings.email.immediately.action'
|
||||
type='select'
|
||||
value={`${Preferences.INTERVAL_IMMEDIATE}`}
|
||||
/>
|
||||
<SettingSeparator/>
|
||||
{enableEmailBatching &&
|
||||
<>
|
||||
<SettingOption
|
||||
action={setNotifyInterval}
|
||||
label={intl.formatMessage({id: 'notification_settings.email.fifteenMinutes', defaultMessage: 'Every 15 minutes'})}
|
||||
selected={notifyInterval === `${Preferences.INTERVAL_FIFTEEN_MINUTES}`}
|
||||
type='select'
|
||||
value={`${Preferences.INTERVAL_FIFTEEN_MINUTES}`}
|
||||
/>
|
||||
<SettingSeparator/>
|
||||
<SettingOption
|
||||
action={setNotifyInterval}
|
||||
label={intl.formatMessage({id: 'notification_settings.email.everyHour', defaultMessage: 'Every hour'})}
|
||||
selected={notifyInterval === `${Preferences.INTERVAL_HOUR}`}
|
||||
type='select'
|
||||
value={`${Preferences.INTERVAL_HOUR}`}
|
||||
/>
|
||||
<SettingSeparator/>
|
||||
</>
|
||||
}
|
||||
<SettingOption
|
||||
action={setNotifyInterval}
|
||||
label={intl.formatMessage({id: 'notification_settings.email.never', defaultMessage: 'Never'})}
|
||||
selected={notifyInterval === `${Preferences.INTERVAL_NEVER}`}
|
||||
testID='notification_settings.email.never.action'
|
||||
type='select'
|
||||
value={`${Preferences.INTERVAL_NEVER}`}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
{!sendEmailNotifications &&
|
||||
<Text
|
||||
style={styles.disabled}
|
||||
>
|
||||
{intl.formatMessage({
|
||||
id: 'notification_settings.email.emailHelp2',
|
||||
defaultMessage: 'Email has been disabled by your System Administrator. No notification emails will be sent until it is enabled.',
|
||||
})}
|
||||
</Text>
|
||||
}
|
||||
</SettingBlock>
|
||||
{isCRTEnabled && notifyProps.email === 'true' && (
|
||||
<SettingBlock
|
||||
footerText={emailFooterCRTText}
|
||||
headerText={emailHeaderCRTText}
|
||||
>
|
||||
<SettingOption
|
||||
action={setEmailThreads}
|
||||
label={intl.formatMessage({id: 'user.settings.notifications.email_threads.description', defaultMessage: 'Notify me about all replies to threads I\'m following'})}
|
||||
selected={emailThreads}
|
||||
type='toggle'
|
||||
/>
|
||||
<SettingSeparator/>
|
||||
</SettingBlock>
|
||||
)}
|
||||
</SettingContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotificationEmail;
|
||||
|
|
@ -3,12 +3,10 @@
|
|||
|
||||
import React, {useCallback, useEffect, useMemo, useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {View} from 'react-native';
|
||||
import {Text} from 'react-native';
|
||||
|
||||
import {updateMe} from '@actions/remote/user';
|
||||
import Block from '@components/block';
|
||||
import FloatingTextInput from '@components/floating_text_input_label';
|
||||
import OptionItem from '@components/option_item';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
|
||||
|
|
@ -19,37 +17,22 @@ import {changeOpacity, getKeyboardAppearanceFromTheme, makeStyleSheetFromTheme}
|
|||
import {typography} from '@utils/typography';
|
||||
import {getNotificationProps} from '@utils/user';
|
||||
|
||||
import {getSaveButton} from '../config';
|
||||
import SettingBlock from '../setting_block';
|
||||
import SettingOption from '../setting_option';
|
||||
import SettingSeparator from '../settings_separator';
|
||||
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
const mentionHeaderText = {
|
||||
id: t('notification_settings.mentions.wordsTrigger'),
|
||||
defaultMessage: 'Words that trigger mentions',
|
||||
id: t('notification_settings.mentions.keywords_mention'),
|
||||
defaultMessage: 'Keywords that trigger mentions',
|
||||
};
|
||||
|
||||
const SAVE_MENTION_BUTTON_ID = 'SAVE_MENTION_BUTTON_ID';
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
separator: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
flex: 1,
|
||||
height: 1,
|
||||
},
|
||||
upperCase: {
|
||||
textTransform: 'uppercase',
|
||||
},
|
||||
label: {
|
||||
color: theme.centerChannelColor,
|
||||
...typography('Body', 200, 'Regular'),
|
||||
},
|
||||
desc: {
|
||||
color: theme.centerChannelColor,
|
||||
...typography('Body', 100, 'Regular'),
|
||||
paddingLeft: 8,
|
||||
},
|
||||
container: {
|
||||
paddingHorizontal: 8,
|
||||
},
|
||||
input: {
|
||||
color: theme.centerChannelColor,
|
||||
height: 150,
|
||||
|
|
@ -58,6 +41,14 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
},
|
||||
containerStyle: {
|
||||
marginTop: 30,
|
||||
width: '90%',
|
||||
alignSelf: 'center',
|
||||
},
|
||||
keywordLabelStyle: {
|
||||
marginLeft: 20,
|
||||
marginTop: 4,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.64),
|
||||
...typography('Body', 75, 'Regular'),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
@ -97,16 +88,7 @@ const MentionSettings = ({componentId, currentUser}: MentionSectionProps) => {
|
|||
const styles = getStyleSheet(theme);
|
||||
const intl = useIntl();
|
||||
|
||||
const saveButton = useMemo(() => {
|
||||
return {
|
||||
id: SAVE_MENTION_BUTTON_ID,
|
||||
enabled: false,
|
||||
showAsAction: 'always' as const,
|
||||
testID: 'notification_settings.mentions.save.button',
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
text: intl.formatMessage({id: 'settings.save', defaultMessage: 'Save'}),
|
||||
};
|
||||
}, [theme.sidebarHeaderTextColor]);
|
||||
const saveButton = useMemo(() => getSaveButton(SAVE_MENTION_BUTTON_ID, intl, theme.sidebarHeaderTextColor), [theme.sidebarHeaderTextColor]);
|
||||
|
||||
const close = () => popTopScreen(componentId);
|
||||
|
||||
|
|
@ -159,44 +141,40 @@ const MentionSettings = ({componentId, currentUser}: MentionSectionProps) => {
|
|||
useAndroidHardwareBackHandler(componentId, close);
|
||||
|
||||
return (
|
||||
<Block
|
||||
<SettingBlock
|
||||
headerText={mentionHeaderText}
|
||||
headerStyles={styles.upperCase}
|
||||
>
|
||||
{ Boolean(currentUser?.firstName) && (
|
||||
{Boolean(currentUser?.firstName) && (
|
||||
<>
|
||||
<OptionItem
|
||||
<SettingOption
|
||||
action={onToggleFirstName}
|
||||
containerStyle={styles.container}
|
||||
description={intl.formatMessage({id: 'notification_settings.mentions.sensitiveName', defaultMessage: 'Your case sensitive first name'})}
|
||||
label={currentUser.firstName}
|
||||
selected={tglFirstName}
|
||||
type='toggle'
|
||||
/>
|
||||
<View style={styles.separator}/>
|
||||
<SettingSeparator/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
{Boolean(currentUser?.username) && (
|
||||
<OptionItem
|
||||
<SettingOption
|
||||
action={onToggleUserName}
|
||||
containerStyle={styles.container}
|
||||
description={intl.formatMessage({id: 'notification_settings.mentions.sensitiveUsername', defaultMessage: 'Your non-case sensitive username'})}
|
||||
label={currentUser.username}
|
||||
selected={tglUserName}
|
||||
type='toggle'
|
||||
/>
|
||||
)}
|
||||
<View style={styles.separator}/>
|
||||
<OptionItem
|
||||
<SettingSeparator/>
|
||||
<SettingOption
|
||||
action={onToggleChannel}
|
||||
containerStyle={styles.container}
|
||||
description={intl.formatMessage({id: 'notification_settings.mentions.channelWide', defaultMessage: 'Channel-wide mentions'})}
|
||||
label='@channel, @all, @here'
|
||||
selected={tglChannel}
|
||||
type='toggle'
|
||||
/>
|
||||
<View style={styles.separator}/>
|
||||
<SettingSeparator/>
|
||||
<FloatingTextInput
|
||||
allowFontScaling={true}
|
||||
autoCapitalize='none'
|
||||
|
|
@ -204,7 +182,7 @@ const MentionSettings = ({componentId, currentUser}: MentionSectionProps) => {
|
|||
blurOnSubmit={true}
|
||||
containerStyle={styles.containerStyle}
|
||||
keyboardAppearance={getKeyboardAppearanceFromTheme(theme)}
|
||||
label={intl.formatMessage({id: 'notification_settings.mentions.keywords', defaultMessage: 'Keywords'})}
|
||||
label={intl.formatMessage({id: 'notification_settings.mentions.keywords', defaultMessage: 'Enter other keywords'})}
|
||||
multiline={true}
|
||||
onChangeText={onChangeText}
|
||||
placeholder={intl.formatMessage({id: 'notification_settings.mentions..keywordsDescription', defaultMessage: 'Other words that trigger a mention'})}
|
||||
|
|
@ -216,7 +194,12 @@ const MentionSettings = ({componentId, currentUser}: MentionSectionProps) => {
|
|||
underlineColorAndroid='transparent'
|
||||
value={mentionKeys}
|
||||
/>
|
||||
</Block>
|
||||
<Text
|
||||
style={styles.keywordLabelStyle}
|
||||
>
|
||||
{intl.formatMessage({id: 'notification_settings.mentions.keywordsLabel', defaultMessage: 'Keywords are not case-sensitive. Separate keywords with commas.'})}
|
||||
</Text>
|
||||
</SettingBlock>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -2,68 +2,28 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {ScrollView} from 'react-native';
|
||||
import {SafeAreaView} from 'react-native-safe-area-context';
|
||||
|
||||
import {useTheme} from '@context/theme';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
import SettingContainer from '../setting_container';
|
||||
|
||||
import MentionSettings from './mention_settings';
|
||||
import ReplySettings from './reply_settings';
|
||||
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
input: {
|
||||
color: theme.centerChannelColor,
|
||||
height: 40,
|
||||
...typography('Body', 75, 'Regular'),
|
||||
},
|
||||
scrollView: {
|
||||
flex: 1,
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.06),
|
||||
},
|
||||
scrollViewContent: {
|
||||
paddingVertical: 35,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
type NotificationMentionProps = {
|
||||
componentId: string;
|
||||
currentUser: UserModel;
|
||||
isCRTEnabled: boolean;
|
||||
}
|
||||
const NotificationMention = ({componentId, currentUser, isCRTEnabled}: NotificationMentionProps) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
return (
|
||||
<SafeAreaView
|
||||
edges={['left', 'right']}
|
||||
testID='notification_mention.screen'
|
||||
style={styles.container}
|
||||
>
|
||||
<ScrollView
|
||||
style={styles.scrollView}
|
||||
contentContainerStyle={styles.scrollViewContent}
|
||||
alwaysBounceVertical={false}
|
||||
>
|
||||
<MentionSettings
|
||||
currentUser={currentUser}
|
||||
componentId={componentId}
|
||||
/>
|
||||
{!isCRTEnabled && (
|
||||
<ReplySettings/>
|
||||
)}
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
<SettingContainer>
|
||||
<MentionSettings
|
||||
currentUser={currentUser}
|
||||
componentId={componentId}
|
||||
/>
|
||||
{!isCRTEnabled && <ReplySettings/>}
|
||||
</SettingContainer>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -3,82 +3,50 @@
|
|||
|
||||
import React, {useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {View} from 'react-native';
|
||||
|
||||
import Block from '@components/block';
|
||||
import OptionItem from '@components/option_item';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {t} from '@i18n';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
import SettingBlock from '../setting_block';
|
||||
import SettingOption from '../setting_option';
|
||||
import SettingSeparator from '../settings_separator';
|
||||
|
||||
const replyHeaderText = {
|
||||
id: t('notification_settings.mention.reply'),
|
||||
defaultMessage: 'Send reply notifications for',
|
||||
};
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
separator: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
flex: 1,
|
||||
height: 1,
|
||||
},
|
||||
upperCase: {
|
||||
textTransform: 'uppercase',
|
||||
},
|
||||
label: {
|
||||
color: theme.centerChannelColor,
|
||||
...typography('Body', 200, 'Regular'),
|
||||
},
|
||||
container: {
|
||||
paddingHorizontal: 8,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const ReplySettings = () => {
|
||||
const [replyNotificationType, setReplyNotificationType] = useState('any'); //todo: initialize with value from db/api
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
const [replyNotificationType, setReplyNotificationType] = useState('any');
|
||||
const intl = useIntl();
|
||||
|
||||
const setReplyNotifications = (notifType: string) => {
|
||||
setReplyNotificationType(notifType);
|
||||
};
|
||||
|
||||
return (
|
||||
<Block
|
||||
<SettingBlock
|
||||
headerText={replyHeaderText}
|
||||
headerStyles={styles.upperCase}
|
||||
>
|
||||
<OptionItem
|
||||
action={setReplyNotifications}
|
||||
type='select'
|
||||
value='any'
|
||||
containerStyle={styles.container}
|
||||
<SettingOption
|
||||
action={setReplyNotificationType}
|
||||
label={intl.formatMessage({id: 'notification_settings.threads_start_participate', defaultMessage: 'Threads that I start or participate in'})}
|
||||
selected={replyNotificationType === 'any'}
|
||||
/>
|
||||
<View style={styles.separator}/>
|
||||
<OptionItem
|
||||
action={setReplyNotifications}
|
||||
type='select'
|
||||
value='root'
|
||||
containerStyle={styles.container}
|
||||
value='any'
|
||||
/>
|
||||
<SettingSeparator/>
|
||||
<SettingOption
|
||||
action={setReplyNotificationType}
|
||||
label={intl.formatMessage({id: 'notification_settings.threads_start', defaultMessage: 'Threads that I start'})}
|
||||
selected={replyNotificationType === 'root'}
|
||||
/>
|
||||
<View style={styles.separator}/>
|
||||
<OptionItem
|
||||
action={setReplyNotifications}
|
||||
type='select'
|
||||
value='never'
|
||||
containerStyle={styles.container}
|
||||
value='root'
|
||||
/>
|
||||
<SettingSeparator/>
|
||||
<SettingOption
|
||||
action={setReplyNotificationType}
|
||||
label={intl.formatMessage({id: 'notification_settings.threads_mentions', defaultMessage: 'Mentions in threads'})}
|
||||
selected={replyNotificationType === 'never'}
|
||||
type='select'
|
||||
value='never'
|
||||
/>
|
||||
</Block>
|
||||
</SettingBlock>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@
|
|||
|
||||
import React, {useCallback, useEffect, useMemo, useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {ScrollView} from 'react-native';
|
||||
import {Edge, SafeAreaView} from 'react-native-safe-area-context';
|
||||
|
||||
import {updateMe} from '@actions/remote/user';
|
||||
import {useServerUrl} from '@context/server';
|
||||
|
|
@ -12,33 +10,17 @@ import {useTheme} from '@context/theme';
|
|||
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
|
||||
import useNavButtonPressed from '@hooks/navigation_button_pressed';
|
||||
import {popTopScreen, setButtons} from '@screens/navigation';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {getNotificationProps} from '@utils/user';
|
||||
|
||||
import {getSaveButton} from '../config';
|
||||
import SettingContainer from '../setting_container';
|
||||
|
||||
import MobileSendPush from './push_send';
|
||||
import MobilePushStatus from './push_status';
|
||||
import MobilePushThread from './push_thread';
|
||||
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
scrollView: {
|
||||
flex: 1,
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.06),
|
||||
},
|
||||
scrollViewContent: {
|
||||
paddingVertical: 30,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const edges: Edge[] = ['left', 'right'];
|
||||
|
||||
const SAVE_NOTIF_BUTTON_ID = 'SAVE_NOTIF_BUTTON_ID';
|
||||
|
||||
type NotificationMobileProps = {
|
||||
|
|
@ -58,22 +40,12 @@ const NotificationPush = ({componentId, currentUser, isCRTEnabled, sendPushNotif
|
|||
|
||||
const intl = useIntl();
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const onMobilePushThreadChanged = useCallback(() => {
|
||||
setPushThreadPref(pushThread === 'all' ? 'mention' : 'all');
|
||||
}, [pushThread]);
|
||||
|
||||
const saveButton = useMemo(() => {
|
||||
return {
|
||||
id: SAVE_NOTIF_BUTTON_ID,
|
||||
enabled: false,
|
||||
showAsAction: 'always' as const,
|
||||
testID: 'notification_settings.save.button',
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
text: intl.formatMessage({id: 'settings.save', defaultMessage: 'Save'}),
|
||||
};
|
||||
}, [theme.sidebarHeaderTextColor]);
|
||||
const saveButton = useMemo(() => getSaveButton(SAVE_NOTIF_BUTTON_ID, intl, theme.sidebarHeaderTextColor), [theme.sidebarHeaderTextColor]);
|
||||
|
||||
const close = useCallback(() => popTopScreen(componentId), [componentId]);
|
||||
|
||||
|
|
@ -110,35 +82,25 @@ const NotificationPush = ({componentId, currentUser, isCRTEnabled, sendPushNotif
|
|||
useAndroidHardwareBackHandler(componentId, close);
|
||||
|
||||
return (
|
||||
<SafeAreaView
|
||||
edges={edges}
|
||||
testID='notification_push.screen'
|
||||
style={styles.container}
|
||||
>
|
||||
<ScrollView
|
||||
style={styles.scrollView}
|
||||
contentContainerStyle={styles.scrollViewContent}
|
||||
alwaysBounceVertical={false}
|
||||
>
|
||||
<MobileSendPush
|
||||
pushStatus={pushSend}
|
||||
sendPushNotifications={sendPushNotifications}
|
||||
setMobilePushPref={setPushSend}
|
||||
<SettingContainer>
|
||||
<MobileSendPush
|
||||
pushStatus={pushSend}
|
||||
sendPushNotifications={sendPushNotifications}
|
||||
setMobilePushPref={setPushSend}
|
||||
/>
|
||||
{isCRTEnabled && pushSend === 'mention' && (
|
||||
<MobilePushThread
|
||||
pushThread={pushThread}
|
||||
onMobilePushThreadChanged={onMobilePushThreadChanged}
|
||||
/>
|
||||
{isCRTEnabled && pushSend === 'mention' && (
|
||||
<MobilePushThread
|
||||
pushThread={pushThread}
|
||||
onMobilePushThreadChanged={onMobilePushThreadChanged}
|
||||
/>
|
||||
)}
|
||||
{sendPushNotifications && pushSend !== 'none' && (
|
||||
<MobilePushStatus
|
||||
pushStatus={pushStatus}
|
||||
setMobilePushStatus={setPushStatus}
|
||||
/>
|
||||
)}
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
)}
|
||||
{sendPushNotifications && pushSend !== 'none' && (
|
||||
<MobilePushStatus
|
||||
pushStatus={pushStatus}
|
||||
setMobilePushStatus={setPushStatus}
|
||||
/>
|
||||
)}
|
||||
</SettingContainer>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -3,49 +3,35 @@
|
|||
|
||||
import React from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {View} from 'react-native';
|
||||
|
||||
import Block from '@components/block';
|
||||
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';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
import SettingBlock from '../setting_block';
|
||||
import SettingOption from '../setting_option';
|
||||
import SettingSeparator from '../settings_separator';
|
||||
|
||||
const headerText = {
|
||||
id: t('notification_settings.send_notification'),
|
||||
defaultMessage: 'Send notifications',
|
||||
id: t('notification_settings.send_notification.about'),
|
||||
defaultMessage: 'Notify me about...',
|
||||
};
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
upperCase: {
|
||||
textTransform: 'uppercase',
|
||||
},
|
||||
separator: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
flex: 1,
|
||||
height: 1,
|
||||
},
|
||||
label: {
|
||||
color: theme.centerChannelColor,
|
||||
...typography('Body', 100, 'Regular'),
|
||||
},
|
||||
disabled: {
|
||||
color: theme.centerChannelColor,
|
||||
paddingHorizontal: 15,
|
||||
paddingVertical: 10,
|
||||
...typography('Body', 200, 'Regular'),
|
||||
},
|
||||
container: {
|
||||
paddingHorizontal: 8,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
type MobileSendPushProps = {
|
||||
sendPushNotifications: boolean;
|
||||
pushStatus: PushStatus;
|
||||
sendPushNotifications: boolean;
|
||||
setMobilePushPref: (status: PushStatus) => void;
|
||||
}
|
||||
const MobileSendPush = ({sendPushNotifications, pushStatus, setMobilePushPref}: MobileSendPushProps) => {
|
||||
|
|
@ -54,36 +40,32 @@ const MobileSendPush = ({sendPushNotifications, pushStatus, setMobilePushPref}:
|
|||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Block
|
||||
<SettingBlock
|
||||
headerText={headerText}
|
||||
headerStyles={styles.upperCase}
|
||||
>
|
||||
{sendPushNotifications &&
|
||||
<>
|
||||
<OptionItem
|
||||
<SettingOption
|
||||
action={setMobilePushPref}
|
||||
containerStyle={styles.container}
|
||||
label={intl.formatMessage({id: 'notification_settings.pushNotification.allActivity', defaultMessage: 'For all activity'})}
|
||||
label={intl.formatMessage({id: 'notification_settings.pushNotification.all_new_messages', defaultMessage: 'All new messages'})}
|
||||
selected={pushStatus === 'all'}
|
||||
testID='notification_settings.pushNotification.allActivity'
|
||||
type='select'
|
||||
value='all'
|
||||
/>
|
||||
<View style={styles.separator}/>
|
||||
<OptionItem
|
||||
<SettingSeparator/>
|
||||
<SettingOption
|
||||
action={setMobilePushPref}
|
||||
containerStyle={styles.container}
|
||||
label={intl.formatMessage({id: 'notification_settings.pushNotification.onlyMentions', defaultMessage: 'Only for mentions and direct messages'})}
|
||||
label={intl.formatMessage({id: 'notification_settings.pushNotification.mentions.only', defaultMessage: 'Mentions, direct messages only(default)'})}
|
||||
selected={pushStatus === 'mention'}
|
||||
testID='notification_settings.pushNotification.onlyMentions'
|
||||
type='select'
|
||||
value='mention'
|
||||
/>
|
||||
<View style={styles.separator}/>
|
||||
<OptionItem
|
||||
<SettingSeparator/>
|
||||
<SettingOption
|
||||
action={setMobilePushPref}
|
||||
containerStyle={styles.container}
|
||||
label={intl.formatMessage({id: 'notification_settings.pushNotification.never', defaultMessage: 'Never'})}
|
||||
label={intl.formatMessage({id: 'notification_settings.pushNotification.nothing', defaultMessage: 'Nothing'})}
|
||||
selected={pushStatus === 'none'}
|
||||
testID='notification_settings.pushNotification.never'
|
||||
type='select'
|
||||
|
|
@ -98,7 +80,7 @@ const MobileSendPush = ({sendPushNotifications, pushStatus, setMobilePushPref}:
|
|||
style={styles.disabled}
|
||||
/>
|
||||
}
|
||||
</Block>
|
||||
</SettingBlock>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -3,82 +3,53 @@
|
|||
|
||||
import React from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {View} from 'react-native';
|
||||
|
||||
import Block from '@components/block';
|
||||
import OptionItem from '@components/option_item';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {t} from '@i18n';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
import SettingBlock from '../setting_block';
|
||||
import SettingOption from '../setting_option';
|
||||
import SettingSeparator from '../settings_separator';
|
||||
|
||||
const headerText = {
|
||||
id: t('notification_settings.mobile.push_status'),
|
||||
defaultMessage: 'Trigger push notifications when',
|
||||
id: t('notification_settings.mobile.trigger_push'),
|
||||
defaultMessage: 'Trigger push notifications when...',
|
||||
};
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
upperCase: {
|
||||
textTransform: 'uppercase',
|
||||
},
|
||||
separator: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
flex: 1,
|
||||
height: 1,
|
||||
},
|
||||
label: {
|
||||
color: theme.centerChannelColor,
|
||||
...typography('Body', 100, 'Regular'),
|
||||
|
||||
},
|
||||
container: {
|
||||
paddingHorizontal: 8,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
type MobilePushStatusProps = {
|
||||
pushStatus: PushStatus;
|
||||
setMobilePushStatus: (status: PushStatus) => void;
|
||||
}
|
||||
const MobilePushStatus = ({pushStatus, setMobilePushStatus}: MobilePushStatusProps) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Block
|
||||
<SettingBlock
|
||||
headerText={headerText}
|
||||
headerStyles={styles.upperCase}
|
||||
>
|
||||
<OptionItem
|
||||
<SettingOption
|
||||
action={setMobilePushStatus}
|
||||
containerStyle={styles.container}
|
||||
label={intl.formatMessage({id: 'notification_settings.mobile.online', defaultMessage: 'Online, away or offline'})}
|
||||
selected={pushStatus === 'online'}
|
||||
type='select'
|
||||
value='online'
|
||||
/>
|
||||
<View style={styles.separator}/>
|
||||
<OptionItem
|
||||
<SettingSeparator/>
|
||||
<SettingOption
|
||||
action={setMobilePushStatus}
|
||||
containerStyle={styles.container}
|
||||
label={intl.formatMessage({id: 'notification_settings.mobile.away', defaultMessage: 'Away or offline'})}
|
||||
selected={pushStatus === 'away'}
|
||||
type='select'
|
||||
value='away'
|
||||
/>
|
||||
<View style={styles.separator}/>
|
||||
<OptionItem
|
||||
<SettingSeparator/>
|
||||
<SettingOption
|
||||
action={setMobilePushStatus}
|
||||
containerStyle={styles.container}
|
||||
label={intl.formatMessage({id: 'notification_settings.mobile.offline', defaultMessage: 'Offline'})}
|
||||
selected={pushStatus === 'offline'}
|
||||
type='select'
|
||||
value='offline'
|
||||
/>
|
||||
</Block>
|
||||
</SettingBlock>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -3,34 +3,18 @@
|
|||
|
||||
import React from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {View} from 'react-native';
|
||||
import {StyleSheet} from 'react-native';
|
||||
|
||||
import Block from '@components/block';
|
||||
import OptionItem from '@components/option_item';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {t} from '@i18n';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
upperCase: {
|
||||
textTransform: 'uppercase',
|
||||
},
|
||||
separator: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
flex: 1,
|
||||
height: 1,
|
||||
marginLeft: 15,
|
||||
},
|
||||
area: {
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
label: {
|
||||
color: theme.centerChannelColor,
|
||||
...typography('Body', 100, 'Regular'),
|
||||
},
|
||||
};
|
||||
import SettingBlock from '../setting_block';
|
||||
import SettingOption from '../setting_option';
|
||||
import SettingSeparator from '../settings_separator';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
area: {
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
});
|
||||
|
||||
const headerText = {
|
||||
|
|
@ -48,25 +32,22 @@ type MobilePushThreadProps = {
|
|||
}
|
||||
|
||||
const MobilePushThread = ({pushThread, onMobilePushThreadChanged}: MobilePushThreadProps) => {
|
||||
const theme = useTheme();
|
||||
const intl = useIntl();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
return (
|
||||
<Block
|
||||
<SettingBlock
|
||||
headerText={headerText}
|
||||
footerText={footerText}
|
||||
headerStyles={styles.upperCase}
|
||||
containerStyles={styles.area}
|
||||
>
|
||||
<OptionItem
|
||||
<SettingOption
|
||||
action={onMobilePushThreadChanged}
|
||||
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>
|
||||
<SettingSeparator/>
|
||||
</SettingBlock>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -3,9 +3,15 @@
|
|||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
import {Preferences} from '@constants';
|
||||
import {getPreferenceValue} from '@helpers/api/preference';
|
||||
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
|
||||
import {observeConfigBooleanValue} from '@queries/servers/system';
|
||||
import {observeIsCRTEnabled} from '@queries/servers/thread';
|
||||
import {observeCurrentUser} from '@queries/servers/user';
|
||||
|
||||
import NotificationSettings from './notifications';
|
||||
|
||||
|
|
@ -13,8 +19,15 @@ import type {WithDatabaseArgs} from '@typings/database/database';
|
|||
|
||||
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
|
||||
return {
|
||||
currentUser: observeCurrentUser(database),
|
||||
isCRTEnabled: observeIsCRTEnabled(database),
|
||||
enableAutoResponder: observeConfigBooleanValue(database, 'ExperimentalEnableAutomaticReplies'),
|
||||
enableEmailBatching: observeConfigBooleanValue(database, 'EnableEmailBatching'),
|
||||
emailInterval: queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_NOTIFICATIONS).
|
||||
observeWithColumns(['value']).pipe(
|
||||
switchMap((preferences) => of$(getPreferenceValue(preferences, Preferences.CATEGORY_NOTIFICATIONS, Preferences.EMAIL_INTERVAL, Preferences.INTERVAL_NOT_SET))),
|
||||
),
|
||||
sendEmailNotifications: observeConfigBooleanValue(database, 'SendEmailNotifications'),
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,41 +1,19 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback} from 'react';
|
||||
import React, {useCallback, useMemo} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Platform, ScrollView, View} from 'react-native';
|
||||
import {Edge, SafeAreaView} from 'react-native-safe-area-context';
|
||||
|
||||
import {Screens} from '@constants';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {General, Screens} from '@constants';
|
||||
import {t} from '@i18n';
|
||||
import {goToScreen} from '@screens/navigation';
|
||||
import SettingOption from '@screens/settings/setting_option';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import SettingRowLabel from '@screens/settings/setting_row_label';
|
||||
import {getEmailInterval, getEmailIntervalTexts, getNotificationProps} from '@utils/user';
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
wrapper: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.06),
|
||||
...Platform.select({
|
||||
ios: {
|
||||
flex: 1,
|
||||
paddingTop: 35,
|
||||
},
|
||||
}),
|
||||
},
|
||||
divider: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
height: 1,
|
||||
width: '100%',
|
||||
},
|
||||
};
|
||||
});
|
||||
const edges: Edge[] = ['left', 'right'];
|
||||
import SettingContainer from '../setting_container';
|
||||
import SettingItem from '../setting_item';
|
||||
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
const mentionTexts = {
|
||||
crtOn: {
|
||||
|
|
@ -47,14 +25,33 @@ const mentionTexts = {
|
|||
defaultMessage: 'Mentions and Replies',
|
||||
},
|
||||
};
|
||||
|
||||
type NotificationsProps = {
|
||||
isCRTEnabled: boolean;
|
||||
currentUser: UserModel;
|
||||
emailInterval: string;
|
||||
enableAutoResponder: boolean;
|
||||
enableEmailBatching: boolean;
|
||||
isCRTEnabled: boolean;
|
||||
sendEmailNotifications: boolean;
|
||||
}
|
||||
const Notifications = ({isCRTEnabled, enableAutoResponder}: NotificationsProps) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
const Notifications = ({
|
||||
currentUser,
|
||||
emailInterval,
|
||||
enableAutoResponder,
|
||||
enableEmailBatching,
|
||||
isCRTEnabled,
|
||||
sendEmailNotifications,
|
||||
}: NotificationsProps) => {
|
||||
const intl = useIntl();
|
||||
const notifyProps = useMemo(() => getNotificationProps(currentUser), [currentUser.notifyProps]);
|
||||
|
||||
const emailIntervalPref = useMemo(() =>
|
||||
getEmailInterval(
|
||||
sendEmailNotifications && notifyProps?.email === 'true',
|
||||
enableEmailBatching,
|
||||
parseInt(emailInterval, 10),
|
||||
).toString(),
|
||||
[emailInterval, enableEmailBatching, notifyProps, sendEmailNotifications]);
|
||||
|
||||
const goToNotificationSettingsMentions = useCallback(() => {
|
||||
const screen = Screens.SETTINGS_NOTIFICATION_MENTION;
|
||||
|
|
@ -85,36 +82,45 @@ const Notifications = ({isCRTEnabled, enableAutoResponder}: NotificationsProps)
|
|||
goToScreen(screen, title);
|
||||
}, []);
|
||||
|
||||
const goToEmailSettings = useCallback(() => {
|
||||
const screen = Screens.SETTINGS_NOTIFICATION_EMAIL;
|
||||
const title = intl.formatMessage({id: 'notification_settings.email', defaultMessage: 'Email Notifications'});
|
||||
goToScreen(screen, title);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<SafeAreaView
|
||||
edges={edges}
|
||||
testID='notification_settings.screen'
|
||||
style={styles.container}
|
||||
>
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.wrapper}
|
||||
alwaysBounceVertical={false}
|
||||
>
|
||||
<View style={styles.divider}/>
|
||||
<SettingOption
|
||||
defaultMessage={isCRTEnabled ? mentionTexts.crtOn.defaultMessage : mentionTexts.crtOff.defaultMessage}
|
||||
i18nId={isCRTEnabled ? mentionTexts.crtOn.id : mentionTexts.crtOff.id}
|
||||
onPress={goToNotificationSettingsMentions}
|
||||
optionName='mentions'
|
||||
/>
|
||||
<SettingOption
|
||||
optionName='push_notification'
|
||||
onPress={goToNotificationSettingsPush}
|
||||
/>
|
||||
{enableAutoResponder && (
|
||||
<SettingOption
|
||||
onPress={goToNotificationAutoResponder}
|
||||
optionName='automatic_dm_replies'
|
||||
<SettingContainer>
|
||||
<SettingItem
|
||||
defaultMessage={isCRTEnabled ? mentionTexts.crtOn.defaultMessage : mentionTexts.crtOff.defaultMessage}
|
||||
i18nId={isCRTEnabled ? mentionTexts.crtOn.id : mentionTexts.crtOff.id}
|
||||
onPress={goToNotificationSettingsMentions}
|
||||
optionName='mentions'
|
||||
/>
|
||||
<SettingItem
|
||||
optionName='push_notification'
|
||||
onPress={goToNotificationSettingsPush}
|
||||
/>
|
||||
<SettingItem
|
||||
optionName='email'
|
||||
onPress={goToEmailSettings}
|
||||
rightComponent={
|
||||
<SettingRowLabel
|
||||
text={intl.formatMessage(getEmailIntervalTexts(emailIntervalPref))}
|
||||
/>
|
||||
)}
|
||||
<View style={styles.divider}/>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
}
|
||||
/>
|
||||
{enableAutoResponder && (
|
||||
<SettingItem
|
||||
onPress={goToNotificationAutoResponder}
|
||||
optionName='automatic_dm_replies'
|
||||
rightComponent={
|
||||
<SettingRowLabel
|
||||
text={currentUser.status === General.OUT_OF_OFFICE && notifyProps.auto_responder_active === 'true' ? 'On' : 'Off'}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</SettingContainer>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
41
app/screens/settings/setting_block.tsx
Normal file
41
app/screens/settings/setting_block.tsx
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React from 'react';
|
||||
|
||||
import Block, {SectionText, BlockProps} from '@components/block';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
blockHeader: {
|
||||
color: theme.centerChannelColor,
|
||||
...typography('Heading', 300, 'SemiBold'),
|
||||
marginBottom: 16,
|
||||
marginLeft: 18,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
type SettingBlockProps = {
|
||||
children: React.ReactNode;
|
||||
headerText?: SectionText;
|
||||
} & BlockProps;
|
||||
|
||||
const SettingBlock = ({headerText, ...props}: SettingBlockProps) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
return (
|
||||
<Block
|
||||
headerText={headerText}
|
||||
headerStyles={styles.blockHeader}
|
||||
{...props}
|
||||
>
|
||||
|
||||
{props.children}
|
||||
</Block>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingBlock;
|
||||
47
app/screens/settings/setting_container.tsx
Normal file
47
app/screens/settings/setting_container.tsx
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {ScrollView} from 'react-native';
|
||||
import {Edge, SafeAreaView} from 'react-native-safe-area-context';
|
||||
|
||||
import {useTheme} from '@context/theme';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
const edges: Edge[] = ['left', 'right'];
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
contentContainerStyle: {
|
||||
marginTop: 20,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
type SettingContainerProps = {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
const SettingContainer = ({children}: SettingContainerProps) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
return (
|
||||
<SafeAreaView
|
||||
edges={edges}
|
||||
style={styles.container}
|
||||
>
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.contentContainerStyle}
|
||||
alwaysBounceVertical={false}
|
||||
>
|
||||
{children}
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingContainer;
|
||||
55
app/screens/settings/setting_item.tsx
Normal file
55
app/screens/settings/setting_item.tsx
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {Platform} from 'react-native';
|
||||
|
||||
import MenuItem, {MenuItemProps} from '@components/menu_item';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
import Options, {DisplayOptionConfig, NotificationsOptionConfig, SettingOptionConfig} from './config';
|
||||
|
||||
type SettingsConfig = keyof typeof SettingOptionConfig | keyof typeof NotificationsOptionConfig| keyof typeof DisplayOptionConfig
|
||||
type SettingOptionProps = {
|
||||
optionName: SettingsConfig;
|
||||
onPress: () => void;
|
||||
} & Omit<MenuItemProps, 'testID'| 'theme'>;
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
return {
|
||||
menuLabel: {
|
||||
color: theme.centerChannelColor,
|
||||
...typography('Body', 200, 'Regular'),
|
||||
},
|
||||
separatorStyle: {
|
||||
width: '91%',
|
||||
alignSelf: 'center',
|
||||
},
|
||||
chevronStyle: {
|
||||
marginRight: 14,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.32),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const SettingItem = ({onPress, optionName, ...rest}: SettingOptionProps) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
const props = {...rest, ...Options[optionName]} as unknown as Omit<MenuItemProps, 'onPress'| 'theme'>;
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
chevronStyle={styles.chevronStyle}
|
||||
labelStyle={styles.menuLabel}
|
||||
onPress={onPress}
|
||||
separator={Platform.OS === 'ios'}
|
||||
separatorStyle={styles.separatorStyle}
|
||||
showArrow={Platform.select({ios: true, default: false})}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingItem;
|
||||
|
|
@ -2,42 +2,38 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {Platform} from 'react-native';
|
||||
|
||||
import MenuItem, {MenuItemProps} from '@components/menu_item';
|
||||
import OptionItem, {OptionItemProps} from '@components/option_item';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
import Options, {DisplayOptionConfig, NotificationsOptionConfig, SettingOptionConfig} from './constant';
|
||||
|
||||
type SettingsConfig = keyof typeof SettingOptionConfig | keyof typeof NotificationsOptionConfig| keyof typeof DisplayOptionConfig
|
||||
type SettingOptionProps = {
|
||||
optionName: SettingsConfig;
|
||||
onPress: () => void;
|
||||
} & Omit<MenuItemProps, 'testID'| 'theme'>;
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
menuLabel: {
|
||||
container: {
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
optionLabelTextStyle: {
|
||||
color: theme.centerChannelColor,
|
||||
...typography('Body', 200),
|
||||
...typography('Body', 200, 'Regular'),
|
||||
marginBottom: 4,
|
||||
},
|
||||
optionDescriptionTextStyle: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.64),
|
||||
...typography('Body', 75, 'Regular'),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const SettingOption = ({onPress, optionName, ...rest}: SettingOptionProps) => {
|
||||
const SettingOption = ({...props}: OptionItemProps) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
const props = {...rest, ...Options[optionName]} as unknown as Omit<MenuItemProps, 'onPress'| 'theme'>;
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
labelStyle={styles.menuLabel}
|
||||
onPress={onPress}
|
||||
separator={true}
|
||||
showArrow={Platform.select({ios: true, default: false})}
|
||||
theme={theme}
|
||||
<OptionItem
|
||||
optionDescriptionTextStyle={styles.optionDescriptionTextStyle}
|
||||
optionLabelTextStyle={styles.optionLabelTextStyle}
|
||||
containerStyle={[styles.container, props.description && {marginTop: 16}]}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
44
app/screens/settings/setting_row_label.tsx
Normal file
44
app/screens/settings/setting_row_label.tsx
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {Platform, Text} from 'react-native';
|
||||
|
||||
import {useTheme} from '@context/theme';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
type SettingRowLabelProps = {
|
||||
text: string;
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
rightLabel: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.56),
|
||||
...typography('Body', 100, 'Regular'),
|
||||
alignSelf: 'center',
|
||||
...Platform.select({
|
||||
android: {
|
||||
marginRight: 20,
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const SettingRowLabel = ({text}: SettingRowLabelProps) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
return (
|
||||
<Text
|
||||
style={styles.rightLabel}
|
||||
>
|
||||
{text}
|
||||
</Text>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingRowLabel;
|
||||
|
|
@ -3,8 +3,7 @@
|
|||
|
||||
import React, {useEffect, useMemo} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Alert, Platform, ScrollView, View} from 'react-native';
|
||||
import {Edge, SafeAreaView} from 'react-native-safe-area-context';
|
||||
import {Alert, Platform, View} from 'react-native';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {Screens} from '@constants';
|
||||
|
|
@ -13,50 +12,27 @@ import {useTheme} from '@context/theme';
|
|||
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
|
||||
import useNavButtonPressed from '@hooks/navigation_button_pressed';
|
||||
import {dismissModal, goToScreen, setButtons} from '@screens/navigation';
|
||||
import SettingContainer from '@screens/settings/setting_container';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
import {tryOpenURL} from '@utils/url';
|
||||
|
||||
import SettingOption from './setting_option';
|
||||
import SettingItem from './setting_item';
|
||||
|
||||
const edges: Edge[] = ['left', 'right'];
|
||||
const CLOSE_BUTTON_ID = 'close-settings';
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
return {
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
wrapper: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.06),
|
||||
...Platform.select({
|
||||
ios: {
|
||||
flex: 1,
|
||||
paddingTop: 35,
|
||||
},
|
||||
}),
|
||||
},
|
||||
divider: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
height: 1,
|
||||
},
|
||||
middleDivider: {
|
||||
borderTopWidth: 1,
|
||||
borderBottomWidth: 1,
|
||||
borderColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
height: 35,
|
||||
},
|
||||
group: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
innerContainerStyle: {
|
||||
containerStyle: {
|
||||
paddingLeft: 8,
|
||||
marginTop: 20,
|
||||
},
|
||||
menuLabel: {
|
||||
color: theme.centerChannelColor,
|
||||
...typography('Body', 200),
|
||||
helpGroup: {
|
||||
width: '91%',
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.08),
|
||||
height: 1,
|
||||
alignSelf: 'center',
|
||||
marginTop: 20,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
@ -68,14 +44,15 @@ type SettingsProps = {
|
|||
siteName: string;
|
||||
}
|
||||
|
||||
//todo: handle display on tablet and Profile the whole feature - https://mattermost.atlassian.net/browse/MM-39711
|
||||
//todo: Profile the whole feature - https://mattermost.atlassian.net/browse/MM-39711
|
||||
|
||||
const Settings = ({componentId, helpLink, showHelp, siteName}: SettingsProps) => {
|
||||
const theme = useTheme();
|
||||
const intl = useIntl();
|
||||
const styles = getStyleSheet(theme);
|
||||
const serverDisplayName = useServerDisplayName();
|
||||
|
||||
const serverName = siteName || serverDisplayName;
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
const closeButton = useMemo(() => {
|
||||
return {
|
||||
|
|
@ -142,59 +119,36 @@ const Settings = ({componentId, helpLink, showHelp, siteName}: SettingsProps) =>
|
|||
}
|
||||
});
|
||||
|
||||
let middleDividerStyle = styles.divider;
|
||||
if (Platform.OS === 'ios') {
|
||||
middleDividerStyle = styles.middleDivider;
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeAreaView
|
||||
edges={edges}
|
||||
style={styles.container}
|
||||
testID='account.screen'
|
||||
>
|
||||
<ScrollView
|
||||
alwaysBounceVertical={false}
|
||||
contentContainerStyle={styles.wrapper}
|
||||
>
|
||||
<View style={styles.divider}/>
|
||||
<View
|
||||
style={styles.group}
|
||||
>
|
||||
<SettingOption
|
||||
optionName='notification'
|
||||
onPress={goToNotifications}
|
||||
/>
|
||||
<SettingOption
|
||||
optionName='display'
|
||||
onPress={goToDisplaySettings}
|
||||
/>
|
||||
<SettingOption
|
||||
optionName='advanced_settings'
|
||||
onPress={goToAdvancedSettings}
|
||||
/>
|
||||
<SettingOption
|
||||
optionName='about'
|
||||
onPress={goToAbout}
|
||||
messageValues={{appTitle: serverName}}
|
||||
separator={Platform.OS === 'ios'}
|
||||
/>
|
||||
</View>
|
||||
<View style={middleDividerStyle}/>
|
||||
<View
|
||||
style={styles.group}
|
||||
>
|
||||
{showHelp &&
|
||||
<SettingOption
|
||||
optionName='help'
|
||||
onPress={openHelp}
|
||||
isLink={true}
|
||||
containerStyle={styles.innerContainerStyle}
|
||||
/>
|
||||
}
|
||||
</View>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
<SettingContainer >
|
||||
<SettingItem
|
||||
onPress={goToNotifications}
|
||||
optionName='notification'
|
||||
/>
|
||||
<SettingItem
|
||||
onPress={goToDisplaySettings}
|
||||
optionName='display'
|
||||
/>
|
||||
<SettingItem
|
||||
onPress={goToAdvancedSettings}
|
||||
optionName='advanced_settings'
|
||||
/>
|
||||
<SettingItem
|
||||
messageValues={{appTitle: serverName}}
|
||||
onPress={goToAbout}
|
||||
optionName='about'
|
||||
/>
|
||||
{Platform.OS === 'android' && <View style={styles.helpGroup}/>}
|
||||
{showHelp &&
|
||||
<SettingItem
|
||||
containerStyle={styles.containerStyle}
|
||||
isLink={true}
|
||||
onPress={openHelp}
|
||||
optionName='help'
|
||||
separator={false}
|
||||
/>
|
||||
}
|
||||
</SettingContainer>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
39
app/screens/settings/settings_separator.tsx
Normal file
39
app/screens/settings/settings_separator.tsx
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {Platform, StyleProp, View, ViewStyle} from 'react-native';
|
||||
|
||||
import {useTheme} from '@context/theme';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
separator: {
|
||||
...Platform.select({
|
||||
ios: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||
width: '91%',
|
||||
alignSelf: 'center',
|
||||
height: 1,
|
||||
marginTop: 12,
|
||||
},
|
||||
default: {
|
||||
display: 'none',
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
});
|
||||
type SettingSeparatorProps = {
|
||||
lineStyles?: StyleProp<ViewStyle>;
|
||||
}
|
||||
|
||||
const SettingSeparator = ({lineStyles}: SettingSeparatorProps) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
return (<View style={[styles.separator, lineStyles]}/>);
|
||||
};
|
||||
|
||||
export default SettingSeparator;
|
||||
|
|
@ -329,7 +329,45 @@ export function getNotificationProps(user: UserModel) {
|
|||
push: 'mention',
|
||||
push_status: 'online',
|
||||
push_threads: 'all',
|
||||
email_threads: 'all',
|
||||
};
|
||||
|
||||
return props;
|
||||
}
|
||||
|
||||
export function getEmailInterval(enableEmailNotification: boolean, enableEmailBatching: boolean, emailIntervalPreference: number): number {
|
||||
const {
|
||||
INTERVAL_NEVER,
|
||||
INTERVAL_IMMEDIATE,
|
||||
INTERVAL_FIFTEEN_MINUTES,
|
||||
INTERVAL_HOUR,
|
||||
} = Preferences;
|
||||
|
||||
const validValuesWithEmailBatching = [INTERVAL_IMMEDIATE, INTERVAL_NEVER, INTERVAL_FIFTEEN_MINUTES, INTERVAL_HOUR];
|
||||
const validValuesWithoutEmailBatching = [INTERVAL_IMMEDIATE, INTERVAL_NEVER];
|
||||
|
||||
if (!enableEmailNotification) {
|
||||
return INTERVAL_NEVER;
|
||||
} else if (enableEmailBatching && validValuesWithEmailBatching.indexOf(emailIntervalPreference) === -1) {
|
||||
// When email batching is enabled, the default interval is 15 minutes
|
||||
return INTERVAL_FIFTEEN_MINUTES;
|
||||
} else if (!enableEmailBatching && validValuesWithoutEmailBatching.indexOf(emailIntervalPreference) === -1) {
|
||||
// When email batching is not enabled, the default interval is immediately
|
||||
return INTERVAL_IMMEDIATE;
|
||||
} else if (enableEmailNotification && emailIntervalPreference === INTERVAL_NEVER) {
|
||||
// When email notification is enabled, the default interval is immediately
|
||||
return INTERVAL_IMMEDIATE;
|
||||
}
|
||||
|
||||
return emailIntervalPreference;
|
||||
}
|
||||
|
||||
export const getEmailIntervalTexts = (interval: string) => {
|
||||
const intervalTexts: Record<string, any> = {
|
||||
[Preferences.INTERVAL_FIFTEEN_MINUTES]: {id: 'notification_settings.email.fifteenMinutes', defaultMessage: 'Every 15 minutes'},
|
||||
[Preferences.INTERVAL_HOUR]: {id: 'notification_settings.email.everyHour', defaultMessage: 'Every hour'},
|
||||
[Preferences.INTERVAL_IMMEDIATE]: {id: 'notification_settings.email.immediately', defaultMessage: 'Immediately'},
|
||||
[Preferences.INTERVAL_NEVER]: {id: 'notification_settings.email.never', defaultMessage: 'Never'},
|
||||
};
|
||||
return intervalTexts[interval];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
"account.settings": "Settings",
|
||||
"account.user_status.title": "User Presence",
|
||||
"account.your_profile": "Your Profile",
|
||||
"advanced_settings.delete_data": "Delete Documents & Data",
|
||||
"alert.channel_deleted.description": "The channel {displayName} has been archived.",
|
||||
"alert.channel_deleted.title": "Archived channel",
|
||||
"alert.push_proxy_error.description": "Due to the configuration for this server, notifications cannot be received in the mobile app. Contact your system admin for more information.",
|
||||
|
|
@ -227,9 +228,13 @@
|
|||
"custom_status.suggestions.working_from_home": "Working from home",
|
||||
"date_separator.today": "Today",
|
||||
"date_separator.yesterday": "Yesterday",
|
||||
"display_settings.clock.military": "24-hour",
|
||||
"display_settings.clock.standard": "12-hour",
|
||||
"display_settings.clockDisplay": "Clock Display",
|
||||
"display_settings.theme": "Theme",
|
||||
"display_settings.timezone": "Timezone",
|
||||
"display_settings.tz.auto": "Auto",
|
||||
"display_settings.tz.manual": "Manual",
|
||||
"download.error": "Unable to download the file. Try again later",
|
||||
"edit_post.editPost": "Edit the post...",
|
||||
"edit_post.save": "Save",
|
||||
|
|
@ -398,8 +403,6 @@
|
|||
"mobile.edit_post.delete_title": "Confirm Post Delete",
|
||||
"mobile.edit_post.error": "There was a problem editing this message. Please try again.",
|
||||
"mobile.edit_post.title": "Editing Message",
|
||||
"mobile.emoji_picker.search.not_found_description": "Check the spelling or try another search.",
|
||||
"mobile.emoji_picker.search.not_found_title": "No results found for \"{searchTerm}\"",
|
||||
"mobile.error_handler.button": "Relaunch",
|
||||
"mobile.error_handler.description": "\nTap relaunch to open the app again. After restart, you can report the problem from the settings menu.\n",
|
||||
"mobile.error_handler.title": "Unexpected error occurred",
|
||||
|
|
@ -569,32 +572,44 @@
|
|||
"msg_typing.isTyping": "{user} is typing...",
|
||||
"notification_settings.auto_responder": "Automatic Replies",
|
||||
"notification_settings.auto_responder.default_message": "Hello, I am out of office and unable to respond to messages.",
|
||||
"notification_settings.auto_responder.enabled": "Enabled",
|
||||
"notification_settings.auto_responder.footer_message": "Set a custom message that will be automatically sent in response to Direct Messages. Mentions in Public and Private Channels will not trigger the automated reply. Enabling Automatic Replies sets your status to Out of Office and disables email and push notifications.",
|
||||
"notification_settings.auto_responder.footer.message": "Set a custom message that is automatically sent in response to direct messages, such as an out of office or vacation reply. Enabling this setting changes your status to Out of Office and disables notifications.",
|
||||
"notification_settings.auto_responder.message": "",
|
||||
"notification_settings.auto_responder.to.enable": "Enable automatic replies",
|
||||
"notification_settings.email": "Email Notifications",
|
||||
"notification_settings.email.crt.emailInfo": "When enabled, any reply to a thread you're following will send an email notification",
|
||||
"notification_settings.email.crt.send": "Thread reply notifications",
|
||||
"notification_settings.email.emailHelp2": "Email has been disabled by your System Administrator. No notification emails will be sent until it is enabled.",
|
||||
"notification_settings.email.emailInfo": "Email notifications are sent for mentions and direct messages when you are offline or away for more than 5 minutes.",
|
||||
"notification_settings.email.everyHour": "Every hour",
|
||||
"notification_settings.email.fifteenMinutes": "Every 15 minutes",
|
||||
"notification_settings.email.immediately": "Immediately",
|
||||
"notification_settings.email.never": "Never",
|
||||
"notification_settings.email.send": "Send email notifications",
|
||||
"notification_settings.mention.reply": "Send reply notifications for",
|
||||
"notification_settings.mentions": "Mentions",
|
||||
"notification_settings.mentions_replies": "Mentions and Replies",
|
||||
"notification_settings.mentions..keywordsDescription": "Other words that trigger a mention",
|
||||
"notification_settings.mentions.channelWide": "Channel-wide mentions",
|
||||
"notification_settings.mentions.keywords": "Keywords",
|
||||
"notification_settings.mentions.keywords_mention": "",
|
||||
"notification_settings.mentions.keywordsLabel": "Keywords are not case-sensitive. Separate keywords with commas.",
|
||||
"notification_settings.mentions.sensitiveName": "Your case sensitive first name",
|
||||
"notification_settings.mentions.sensitiveUsername": "Your non-case sensitive username",
|
||||
"notification_settings.mentions.wordsTrigger": "Words that trigger mentions",
|
||||
"notification_settings.mobile": "Push Notifications",
|
||||
"notification_settings.mobile.away": "Away or offline",
|
||||
"notification_settings.mobile.offline": "Offline",
|
||||
"notification_settings.mobile.online": "Online, away or offline",
|
||||
"notification_settings.mobile.push_status": "Trigger push notifications when",
|
||||
"notification_settings.ooo_auto_responder": "Automatic Direct Message Replies",
|
||||
"notification_settings.mobile.trigger_push": "Trigger push notifications when...",
|
||||
"notification_settings.ooo_auto_responder": "Automatic replies",
|
||||
"notification_settings.push_notification": "Push Notifications",
|
||||
"notification_settings.push_threads": "Thread reply notifications",
|
||||
"notification_settings.push_threads.description": "Notify me about all replies to threads I'm following",
|
||||
"notification_settings.push_threads.info": "When enabled, any reply to a thread you're following will send a mobile push notification",
|
||||
"notification_settings.pushNotification.allActivity": "For all activity",
|
||||
"notification_settings.pushNotification.all_new_messages": "All new messages",
|
||||
"notification_settings.pushNotification.disabled_long": "Push notifications for mobile devices have been disabled by your System Administrator.",
|
||||
"notification_settings.pushNotification.never": "Never",
|
||||
"notification_settings.pushNotification.onlyMentions": "Only for mentions and direct messages",
|
||||
"notification_settings.send_notification": "Send notifications",
|
||||
"notification_settings.pushNotification.mentions.only": "Mentions, direct messages only(default)",
|
||||
"notification_settings.pushNotification.nothing": "Nothing",
|
||||
"notification_settings.send_notification.about": "Notify me about...",
|
||||
"notification_settings.threads_mentions": "Mentions in threads",
|
||||
"notification_settings.threads_start": "Threads that I start",
|
||||
"notification_settings.threads_start_participate": "Threads that I start or participate in",
|
||||
|
|
@ -707,7 +722,6 @@
|
|||
"settings.about": "About {appTitle}",
|
||||
"settings.advanced_settings": "Advanced Settings",
|
||||
"settings.display": "Display",
|
||||
"settings.display.militaryClock.save": "Save",
|
||||
"settings.notifications": "Notifications",
|
||||
"settings.save": "Save",
|
||||
"snack.bar.favorited.channel": "This channel was favorited",
|
||||
|
|
@ -766,6 +780,7 @@
|
|||
"user.settings.general.nickname": "Nickname",
|
||||
"user.settings.general.position": "Position",
|
||||
"user.settings.general.username": "Username",
|
||||
"user.settings.notifications.email_threads.description": "Notify me about all replies to threads I'm following",
|
||||
"user.tutorial.long_press": "Long-press on an item to view a user's profile",
|
||||
"video.download": "Download video",
|
||||
"video.failed_description": "An error occurred while trying to play the video.\n",
|
||||
|
|
|
|||
1
types/api/users.d.ts
vendored
1
types/api/users.d.ts
vendored
|
|
@ -17,6 +17,7 @@ type UserNotifyProps = {
|
|||
push_status: 'ooo' | 'offline' | 'away' | 'dnd' | 'online';
|
||||
user_id?: string;
|
||||
push_threads?: 'all' | 'mention';
|
||||
email_threads?: 'all' | 'mention';
|
||||
};
|
||||
|
||||
type UserProfile = {
|
||||
|
|
|
|||
Loading…
Reference in a new issue