MM-39711 - Gekidou Notification Push Notification main screen (#6312)

* added chevron to menu item component

* starting with the skeleton

* starting with the skeleton

* starting with the skeleton

* starting with the skeleton

* remove extra line

* tested on tablets

* some corrections

* corrections as per review

* starting with notification skeleton

* attached notification settings to navigation

* added auto responder

* update translation

* update snapshot

* updated snapshot

* correction after review

* removed unnecessary screen

* refactored

* updated the testIDs

* Update Package.resolved

* refactor

* removed Mattermost as default server name

* fix ts

* refactored settings constant

* display settings skeleton

- pending: query for allowed themes

* added 'allowedThemes' query

* added section item

* mention screen skeleton in place

* added section and sectionItem component

* added reply section to the mention screen

* update i18n

* rename screens properly

* update i18n

* Refactored to MentionSettings component

* Refactored to ReplySettings component

* style clean up

* textTransform uppercase

* rename Section/SectionItem to Block/BlockItem

* added mobile push notif screen - push status section

* adding text to those two components

* correction following review

* added mobile push notification section

* added mobile push notification thread section

* style fix

* code fix

* code fix

* MM-39711 - Gekidou Notification - Auto Responder main screen (#6313)

* added skeleton for auto responder

* code clean up

* correction from peer review

* correction from PR review

* clean up

* PR review correction

* correction

* clean up

* clean up

* code clean up

* code clean up
This commit is contained in:
Avinash Lingaloo 2022-06-03 16:02:40 +04:00 committed by GitHub
parent 18a94f2b33
commit 2d3d43c000
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 699 additions and 126 deletions

View file

@ -2,7 +2,7 @@
// See LICENSE.txt for license information.
import React, {ReactElement, useCallback, useMemo} from 'react';
import {Switch, Text, TouchableOpacity, View} from 'react-native';
import {StyleProp, Switch, Text, TextStyle, TouchableOpacity, View, ViewStyle} from 'react-native';
import CompassIcon from '@components/compass_icon';
import {useTheme} from '@context/theme';
@ -36,15 +36,12 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
},
label: {
color: theme.centerChannelColor,
...typography('Body', 600, 'SemiBold'),
fontSize: 16,
lineHeight: 24,
...typography('Body', 200, 'SemiBold'),
marginLeft: 9,
},
description: {
color: changeOpacity(theme.centerChannelColor, 0.6),
...typography('Body', 400, 'Regular'),
fontSize: 12,
lineHeight: 16,
...typography('Body', 75, 'Regular'),
marginTop: 3,
},
arrow: {
@ -62,14 +59,29 @@ type Props = {
action: (value: string | boolean) => void;
actionType: string;
actionValue?: string;
containerStyle?: StyleProp<ViewStyle>;
description?: string | ReactElement;
descriptionStyle?: StyleProp<TextStyle>;
icon?: string;
label: string | ReactElement;
labelStyle?: StyleProp<TextStyle>;
selected?: boolean;
testID?: string;
}
const BlockItem = ({testID = 'sectionItem', action, actionType, actionValue, label, selected, description, icon}: Props) => {
const BlockItem = ({
action,
actionType,
actionValue,
containerStyle,
description,
descriptionStyle,
icon,
label,
labelStyle,
selected,
testID = 'sectionItem',
}: Props) => {
const theme = useTheme();
const style = getStyleSheet(theme);
@ -104,17 +116,17 @@ const BlockItem = ({testID = 'sectionItem', action, actionType, actionValue, lab
action(actionValue || '');
}, [actionValue, action]);
const labelStyle = useMemo(() => {
const labelStyles = useMemo(() => {
if (icon) {
return [style.label, {marginLeft: 4}];
}
return style.label;
return [style.label, labelStyle];
}, [Boolean(icon), style]);
const component = (
<View
testID={testID}
style={style.container}
style={[style.container, containerStyle]}
>
<View style={description ? style.doubleContainer : style.singleContainer}>
<View style={style.labelContainer}>
@ -126,14 +138,14 @@ const BlockItem = ({testID = 'sectionItem', action, actionType, actionValue, lab
/>
)}
<Text
style={labelStyle}
style={labelStyles}
testID={`${testID}.label`}
>
{label}
</Text>
</View>
<Text
style={style.description}
style={[style.description, descriptionStyle]}
testID={`${testID}.description`}
>
{description}

View file

@ -44,7 +44,9 @@ export const SERVER = 'Server';
export const SETTINGS = 'Settings';
export const SETTINGS_DISPLAY = 'SettingsDisplay';
export const SETTINGS_NOTIFICATION = 'SettingsNotification';
export const SETTINGS_NOTIFICATION_AUTO_RESPONDER = 'SettingsNotificationAutoResponder';
export const SETTINGS_NOTIFICATION_MENTION = 'SettingsNotificationMention';
export const SETTINGS_NOTIFICATION_PUSH = 'SettingsNotificationPush';
export const SNACK_BAR = 'SnackBar';
export const SSO = 'SSO';
export const THREAD = 'Thread';
@ -95,7 +97,9 @@ export default {
SETTINGS,
SETTINGS_DISPLAY,
SETTINGS_NOTIFICATION,
SETTINGS_NOTIFICATION_AUTO_RESPONDER,
SETTINGS_NOTIFICATION_MENTION,
SETTINGS_NOTIFICATION_PUSH,
SNACK_BAR,
SSO,
THREAD,

View file

@ -197,6 +197,12 @@ Navigation.setLazyComponentRegistrator((screenName) => {
case Screens.SETTINGS_NOTIFICATION_MENTION:
screen = withServerDatabase(require('@screens/settings/notification_mention').default);
break;
case Screens.SETTINGS_NOTIFICATION_PUSH:
screen = withServerDatabase(require('@screens/settings/notification_push').default);
break;
case Screens.SETTINGS_NOTIFICATION_AUTO_RESPONDER:
screen = withServerDatabase(require('@screens/settings/notification_auto_responder').default);
break;
}
if (screen) {

View file

@ -43,13 +43,13 @@ export const NotificationsOptionConfig = {
},
push_notification: {
defaultMessage: 'Push Notifications',
i18nId: t('mobile.notification_settings.mobile'),
i18nId: t('notification_settings.mobile'),
iconName: 'cellphone',
testID: 'notification_settings.push_notification',
},
automatic_dm_replies: {
defaultMessage: 'Automatic Direct Message Replies',
i18nId: t('mobile.notification_settings.ooo_auto_responder'),
i18nId: t('notification_settings.ooo_auto_responder'),
iconName: 'reply-outline',
testID: 'notification_settings.automatic_dm_replies',
},

View file

@ -49,7 +49,7 @@ const Display = ({isTimezoneEnabled, isThemeSwitchingEnabled}: DisplayProps) =>
return (
<SafeAreaView
edges={['left', 'right']}
testID='notification_settings.screen'
testID='notification_display.screen'
style={styles.container}
>
<ScrollView

View file

@ -0,0 +1,18 @@
// 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 {observeCurrentUser} from '@queries/servers/user';
import {WithDatabaseArgs} from '@typings/database/database';
import NotificationAutoResponder from './notification_auto_responder';
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
return {
currentUser: observeCurrentUser(database),
};
});
export default withDatabase(enhanced(NotificationAutoResponder));

View file

@ -0,0 +1,154 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useCallback, useMemo, useRef, useState} from 'react';
import {useIntl} from 'react-intl';
import {View} from 'react-native';
import {Edge, SafeAreaView} from 'react-native-safe-area-context';
import BlockItem from '@components/block_item';
import FloatingTextInput from '@components/floating_text_input_label';
import FormattedText from '@components/formatted_text';
import {General} from '@constants';
import {useTheme} from '@context/theme';
import {t} from '@i18n';
import {changeOpacity, makeStyleSheetFromTheme, getKeyboardAppearanceFromTheme} from '@utils/theme';
import {typography} from '@utils/typography';
import {getNotificationProps} from '@utils/user';
import type UserModel from '@typings/database/models/servers/user';
const headerText = {
id: t('notification_settings.auto_responder'),
defaultMessage: 'Custom message',
};
const OOO = {
id: t('notification_settings.auto_responder.default_message'),
defaultMessage: 'Hello, I am out of office and unable to respond to messages.',
};
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,
fontSize: 15,
height: 150,
paddingHorizontal: 15,
paddingVertical: 10,
},
footer: {
paddingHorizontal: 15,
color: changeOpacity(theme.centerChannelColor, 0.5),
textAlign: 'justify',
...typography('Body', 75),
},
area: {
paddingHorizontal: 16,
},
label: {
color: theme.centerChannelColor,
...typography('Body', 200, 'Regular'),
},
enabled: {
paddingHorizontal: 8,
backgroundColor: theme.centerChannelBg,
marginBottom: 16,
},
};
});
const edges: Edge[] = ['left', 'right'];
type NotificationAutoResponderProps = {
currentUser: UserModel;
}
const NotificationAutoResponder = ({currentUser}: NotificationAutoResponderProps) => {
const theme = useTheme();
const autoresponderRef = useRef(null);
const intl = useIntl();
const userNotifyProps = useMemo(() => getNotificationProps(currentUser), [currentUser.notifyProps]);
const [notifyProps, setNotifyProps] = useState<Partial<UserNotifyProps>>({
...userNotifyProps,
auto_responder_active: (currentUser.status === General.OUT_OF_OFFICE && userNotifyProps.auto_responder_active) ? 'true' : 'false',
auto_responder_message: userNotifyProps.auto_responder_message || intl.formatMessage(OOO),
});
const styles = getStyleSheet(theme);
const onAutoResponseToggle = useCallback((active: boolean) => {
setNotifyProps({
...notifyProps,
auto_responder_active: `${active}`,
});
}, [notifyProps]);
const onAutoResponseChangeText = useCallback((message: string) => {
setNotifyProps({
...notifyProps,
auto_responder_message: message,
});
}, [notifyProps]);
return (
<SafeAreaView
edges={edges}
style={styles.container}
>
<View style={styles.wrapper}>
<View
style={styles.enabled}
>
<BlockItem
label={
<FormattedText
id='notification_settings.auto_responder.enabled'
defaultMessage='Enabled'
style={styles.label}
/>}
action={onAutoResponseToggle}
actionType='toggle'
selected={notifyProps.auto_responder_active === 'true'}
/>
</View>
{notifyProps.auto_responder_active === '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)}
ref={autoresponderRef}
returnKeyType='done'
style={styles.input}
textAlignVertical='top'
theme={theme}
underlineColorAndroid='transparent'
value={notifyProps.auto_responder_message || ''}
/>
)}
<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}
/>
</View>
</SafeAreaView>
);
};
export default NotificationAutoResponder;

View file

@ -2,11 +2,11 @@
// See LICENSE.txt for license information.
import React, {useReducer} from 'react';
import {Alert, Text, View} from 'react-native';
import {useIntl} from 'react-intl';
import {Alert, View} from 'react-native';
import Block from '@components/block';
import BlockItem from '@components/block_item';
import FormattedText from '@components/formatted_text';
import {useTheme} from '@context/theme';
import {t} from '@i18n';
import UserModel from '@typings/database/models/servers/user';
@ -47,19 +47,21 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
flex: 1,
height: 1,
marginLeft: 15,
},
area: {
paddingHorizontal: 16,
},
upperCase: {
textTransform: 'uppercase',
},
label: {
color: theme.centerChannelColor,
...typography('Body', 400, 'Regular'),
fontSize: 16,
lineHeight: 24,
...typography('Body', 200, 'Regular'),
},
desc: {
color: theme.centerChannelColor,
...typography('Body', 100, 'Regular'),
paddingLeft: 8,
},
container: {
paddingHorizontal: 8,
},
};
});
@ -72,18 +74,7 @@ const MentionSettings = ({currentUser, mentionKeys}: MentionSectionProps) => {
const [{firstName, usernameMention, channel}, dispatch] = useReducer(reducer, INITIAL_STATE);
const theme = useTheme();
const styles = getStyleSheet(theme);
let mentionKeysComponent;
if (mentionKeys) {
mentionKeysComponent = (<Text>{mentionKeys}</Text>);
} else {
mentionKeysComponent = (
<FormattedText
id='notification_settings.mentions.keywordsDescription'
defaultMessage='Other words that trigger a mention'
/>
);
}
const intl = useIntl();
const toggleChannelMentions = () => {
dispatch({
@ -119,24 +110,17 @@ const MentionSettings = ({currentUser, mentionKeys}: MentionSectionProps) => {
<Block
headerText={mentionHeaderText}
headerStyles={styles.upperCase}
containerStyles={styles.area}
>
{ Boolean(currentUser?.firstName) && (
<>
<BlockItem
label={(
<Text style={styles.label}>
{currentUser!.firstName}
</Text>
)}
description={(
<FormattedText
id='notification_settings.mentions.sensitiveName'
defaultMessage='Your case sensitive first name'
/>
)}
action={toggleFirstNameMention}
actionType='toggle'
containerStyle={styles.container}
description={intl.formatMessage({id: 'notification_settings.mentions.sensitiveName', defaultMessage: 'Your case sensitive first name'})}
descriptionStyle={styles.desc}
label={currentUser!.firstName}
labelStyle={styles.label}
selected={firstName}
/>
<View style={styles.separator}/>
@ -145,51 +129,36 @@ const MentionSettings = ({currentUser, mentionKeys}: MentionSectionProps) => {
}
{Boolean(currentUser?.username) && (
<BlockItem
label={(
<Text style={styles.label}>
{currentUser!.username}
</Text>
)}
description={(
<FormattedText
id='notification_settings.mentions.sensitiveUsername'
defaultMessage='Your non-case sensitive username'
/>
)}
selected={usernameMention}
action={toggleUsernameMention}
actionType='toggle'
containerStyle={styles.container}
description={intl.formatMessage({id: 'notification_settings.mentions.sensitiveUsername', defaultMessage: 'Your non-case sensitive username'})}
descriptionStyle={styles.desc}
label={currentUser!.username}
labelStyle={styles.label}
selected={usernameMention}
/>
)}
<View style={styles.separator}/>
<BlockItem
label={(
<Text style={styles.label}>
{'@channel, @all, @here'}
</Text>
)}
description={(
<FormattedText
id='notification_settings.mentions.channelWide'
defaultMessage='Channel-wide mentions'
/>
)}
action={toggleChannelMentions}
actionType='toggle'
containerStyle={styles.container}
description={intl.formatMessage({id: 'notification_settings.mentions.channelWide', defaultMessage: 'Channel-wide mentions'})}
descriptionStyle={styles.desc}
label='@channel, @all, @here'
labelStyle={styles.label}
selected={channel}
/>
<View style={styles.separator}/>
<BlockItem
label={(
<FormattedText
id='notification_settings.mentions.keywords'
defaultMessage='Keywords'
style={styles.label}
/>
)}
description={mentionKeysComponent}
action={goToNotificationSettingsMentionKeywords}
actionType='arrow'
containerStyle={styles.container}
description={mentionKeys || intl.formatMessage({id: 'notification_settings.mentions.keywordsDescription', defaultMessage: 'Other words that trigger a mention'})}
descriptionStyle={styles.desc}
label={intl.formatMessage({id: 'notification_settings.mentions.keywords', defaultMessage: 'Keywords'})}
labelStyle={styles.label}
/>
</Block>
);

View file

@ -46,7 +46,7 @@ const NotificationMention = ({currentUser, mentionKeys, isCRTEnabled}: Notificat
return (
<SafeAreaView
edges={['left', 'right']}
testID='notification_settings.screen'
testID='notification_mention.screen'
style={styles.container}
>
<ScrollView

View file

@ -2,11 +2,11 @@
// See LICENSE.txt for license information.
import React, {useState} from 'react';
import {useIntl} from 'react-intl';
import {View} from 'react-native';
import Block from '@components/block';
import BlockItem from '@components/block_item';
import FormattedText from '@components/formatted_text';
import {useTheme} from '@context/theme';
import {t} from '@i18n';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
@ -19,23 +19,20 @@ const replyHeaderText = {
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return {
area: {
paddingHorizontal: 16,
},
separator: {
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
flex: 1,
height: 1,
marginLeft: 15,
},
upperCase: {
textTransform: 'uppercase',
},
label: {
color: theme.centerChannelColor,
...typography('Body', 400, 'Regular'),
fontSize: 16,
lineHeight: 24,
...typography('Body', 200, 'Regular'),
},
container: {
paddingHorizontal: 8,
},
};
});
@ -44,6 +41,7 @@ const ReplySettings = () => {
const [replyNotificationType, setReplyNotificationType] = useState('any'); //todo: initialize with value from db/api
const theme = useTheme();
const styles = getStyleSheet(theme);
const intl = useIntl();
const setReplyNotifications = (notifType: string) => {
setReplyNotificationType(notifType);
@ -52,48 +50,35 @@ const ReplySettings = () => {
return (
<Block
headerText={replyHeaderText}
containerStyles={styles.area}
headerStyles={styles.upperCase}
>
<BlockItem
label={(
<FormattedText
id='notification_settings.threads_start_participate'
defaultMessage='Threads that I start or participate in'
style={styles.label}
/>
)}
action={setReplyNotifications}
actionType='select'
actionValue='any'
containerStyle={styles.container}
label={intl.formatMessage({id: 'notification_settings.threads_start_participate', defaultMessage: 'Threads that I start or participate in'})}
labelStyle={styles.label}
selected={replyNotificationType === 'any'}
/>
<View style={styles.separator}/>
<BlockItem
label={(
<FormattedText
id='notification_settings.threads_start'
defaultMessage='Threads that I start'
style={styles.label}
/>
)}
action={setReplyNotifications}
actionType='select'
actionValue='root'
containerStyle={styles.container}
label={intl.formatMessage({id: 'notification_settings.threads_start', defaultMessage: 'Threads that I start'})}
labelStyle={styles.label}
selected={replyNotificationType === 'root'}
/>
<View style={styles.separator}/>
<BlockItem
label={(
<FormattedText
id='notification_settings.threads_mentions'
defaultMessage='Mentions in threads'
style={styles.label}
/>
)}
action={setReplyNotifications}
actionType='select'
actionValue='never'
containerStyle={styles.container}
label={intl.formatMessage({id: 'notification_settings.threads_mentions', defaultMessage: 'Mentions in threads'})}
labelStyle={styles.label}
selected={replyNotificationType === 'never'}
/>
</Block>

View file

@ -0,0 +1,20 @@
// 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 {observeConfigBooleanValue} from '@queries/servers/system';
import {observeIsCRTEnabled} from '@queries/servers/thread';
import {WithDatabaseArgs} from '@typings/database/database';
import NotificationPush from './notification_push';
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
return {
sendPushNotifications: observeConfigBooleanValue(database, 'SendPushNotifications'),
isCRTEnabled: observeIsCRTEnabled(database),
};
});
export default withDatabase(enhanced(NotificationPush));

View file

@ -0,0 +1,89 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useState} from 'react';
import {ScrollView} from 'react-native';
import {Edge, SafeAreaView} from 'react-native-safe-area-context';
import {useTheme} from '@context/theme';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import MobileSendPush from './push_send';
import MobilePushStatus from './push_status';
import MobilePushThread from './push_thread';
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'];
type NotificationMobileProps = {
isCRTEnabled: boolean;
sendPushNotifications: boolean;
};
const NotificationPush = ({isCRTEnabled, sendPushNotifications}: NotificationMobileProps) => {
const theme = useTheme();
const [pushStatus, setPushStatus] = useState<PushStatus>('online');
const [pushPref, setPushPref] = useState<PushStatus>('online');
const [pushThread, setPushThreadPref] = useState<PushStatus>('online');
const styles = getStyleSheet(theme);
const setMobilePushStatus = (status: PushStatus) => {
setPushStatus(status);
};
const setMobilePushPref = (status: PushStatus) => {
setPushPref(status);
};
const onMobilePushThreadChanged = (status: PushStatus) => {
setPushThreadPref(status);
};
return (
<SafeAreaView
edges={edges}
testID='notification_push.screen'
style={styles.container}
>
<ScrollView
style={styles.scrollView}
contentContainerStyle={styles.scrollViewContent}
alwaysBounceVertical={false}
>
<MobileSendPush
sendPushNotifications={sendPushNotifications}
pushStatus={pushPref}
setMobilePushPref={setMobilePushPref}
/>
{isCRTEnabled && pushPref === 'mention' && (
<MobilePushThread
pushThread={pushThread}
onMobilePushThreadChanged={onMobilePushThreadChanged}
/>
)}
{sendPushNotifications && pushPref !== 'none' && (
<MobilePushStatus
pushStatus={pushStatus}
setMobilePushStatus={setMobilePushStatus}
/>
)}
</ScrollView>
</SafeAreaView>
);
};
export default NotificationPush;

View file

@ -0,0 +1,102 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {useIntl} from 'react-intl';
import {View} from 'react-native';
import Block from '@components/block';
import BlockItem from '@components/block_item';
import FormattedText from '@components/formatted_text';
import {useTheme} from '@context/theme';
import {t} from '@i18n';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography';
const headerText = {
id: t('notification_settings.send_notification'),
defaultMessage: 'Send notifications',
};
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,
fontSize: 15,
paddingHorizontal: 15,
paddingVertical: 10,
},
};
});
type MobileSendPushProps = {
sendPushNotifications: boolean;
pushStatus: PushStatus;
setMobilePushPref: (status: PushStatus) => void;
}
const MobileSendPush = ({sendPushNotifications, pushStatus, setMobilePushPref}: MobileSendPushProps) => {
const theme = useTheme();
const styles = getStyleSheet(theme);
const intl = useIntl();
return (
<Block
headerText={headerText}
headerStyles={styles.upperCase}
>
{sendPushNotifications &&
<>
<BlockItem
action={setMobilePushPref}
actionType='select'
actionValue='all'
label={intl.formatMessage({id: 'notification_settings.pushNotification.allActivity', defaultMessage: 'For all activity'})}
labelStyle={styles.label}
selected={pushStatus === 'all'}
testID='notification_settings.pushNotification.allActivity'
/>
<View style={styles.separator}/>
<BlockItem
action={setMobilePushPref}
actionType='select'
actionValue='mention'
label={intl.formatMessage({id: 'notification_settings.pushNotification.onlyMentions', defaultMessage: 'Only for mentions and direct messages'})}
labelStyle={styles.label}
selected={pushStatus === 'mention'}
testID='notification_settings.pushNotification.onlyMentions'
/>
<View style={styles.separator}/>
<BlockItem
action={setMobilePushPref}
actionType='select'
actionValue='none'
label={intl.formatMessage({id: 'notification_settings.pushNotification.never', defaultMessage: 'Never'})}
labelStyle={styles.label}
selected={pushStatus === 'none'}
testID='notification_settings.pushNotification.never'
/>
</>
}
{!sendPushNotifications &&
<FormattedText
defaultMessage='Push notifications for mobile devices have been disabled by your System Administrator.'
id='notification_settings.pushNotification.disabled_long'
style={styles.disabled}
/>
}
</Block>
);
};
export default MobileSendPush;

View file

@ -0,0 +1,82 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {useIntl} from 'react-intl';
import {View} from 'react-native';
import Block from '@components/block';
import BlockItem from '@components/block_item';
import {useTheme} from '@context/theme';
import {t} from '@i18n';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography';
const headerText = {
id: t('notification_settings.mobile.push_status'),
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'),
},
};
});
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
headerText={headerText}
headerStyles={styles.upperCase}
>
<BlockItem
label={intl.formatMessage({id: 'notification_settings.mobile.online', defaultMessage: 'Online, away or offline'})}
labelStyle={styles.label}
action={setMobilePushStatus}
actionType='select'
actionValue='online'
selected={pushStatus === 'online'}
/>
<View style={styles.separator}/>
<BlockItem
label={intl.formatMessage({id: 'notification_settings.mobile.away', defaultMessage: 'Away or offline'})}
labelStyle={styles.label}
action={setMobilePushStatus}
actionType='select'
actionValue='away'
selected={pushStatus === 'away'}
/>
<View style={styles.separator}/>
<BlockItem
label={intl.formatMessage({id: 'notification_settings.mobile.offline', defaultMessage: 'Offline'})}
labelStyle={styles.label}
action={setMobilePushStatus}
actionType='select'
actionValue='offline'
selected={pushStatus === 'offline'}
/>
</Block>
);
};
export default MobilePushStatus;

View file

@ -0,0 +1,78 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {View} from 'react-native';
import Block from '@components/block';
import BlockItem from '@components/block_item';
import FormattedText from '@components/formatted_text';
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'),
},
};
});
const headerText = {
id: t('notification_settings.push_threads'),
defaultMessage: 'Thread reply notifications',
};
const footerText = {
id: t('notification_settings.push_threads.info'),
defaultMessage: 'When enabled, any reply to a thread you\'re following will send a mobile push notification',
};
type MobilePushThreadProps = {
onMobilePushThreadChanged: (status: string) => void;
pushThread: PushStatus;
}
const MobilePushThread = ({pushThread, onMobilePushThreadChanged}: MobilePushThreadProps) => {
const theme = useTheme();
const styles = getStyleSheet(theme);
return (
<Block
headerText={headerText}
footerText={footerText}
headerStyles={styles.upperCase}
containerStyles={styles.area}
>
<BlockItem
label={(
<FormattedText
id='notification_settings.push_threads.description'
defaultMessage={'Notify me about all replies to threads I\'m following'}
style={styles.label}
/>
)}
action={onMobilePushThreadChanged}
actionType='toggle'
selected={pushThread === 'all'}
/>
<View style={styles.separator}/>
</Block>
);
};
export default MobilePushThread;

View file

@ -3,7 +3,7 @@
import React from 'react';
import {useIntl} from 'react-intl';
import {Alert, Platform, ScrollView, View} from 'react-native';
import {Platform, ScrollView, View} from 'react-native';
import {Edge, SafeAreaView} from 'react-native-safe-area-context';
import {Screens} from '@constants';
@ -53,12 +53,6 @@ const Notifications = ({isCRTEnabled, enableAutoResponder}: NotificationsProps)
mentionsI18nDefault = 'Mentions';
}
const onPressHandler = () => {
return Alert.alert(
'The functionality you are trying to use has not yet been implemented.',
);
};
const goToNotificationSettingsMentions = () => {
const screen = Screens.SETTINGS_NOTIFICATION_MENTION;
@ -69,6 +63,25 @@ const Notifications = ({isCRTEnabled, enableAutoResponder}: NotificationsProps)
goToScreen(screen, title);
};
const goToNotificationSettingsPush = () => {
const screen = Screens.SETTINGS_NOTIFICATION_PUSH;
const title = intl.formatMessage({
id: 'notification_settings.push_notification',
defaultMessage: 'Push Notifications',
});
goToScreen(screen, title);
};
const goToNotificationAutoResponder = () => {
const screen = Screens.SETTINGS_NOTIFICATION_AUTO_RESPONDER;
const title = intl.formatMessage({
id: 'notification_settings.auto_responder',
defaultMessage: 'Automatic Replies',
});
goToScreen(screen, title);
};
return (
<SafeAreaView
edges={edges}
@ -88,11 +101,11 @@ const Notifications = ({isCRTEnabled, enableAutoResponder}: NotificationsProps)
/>
<SettingOption
optionName='push_notification'
onPress={onPressHandler}
onPress={goToNotificationSettingsPush}
/>
{enableAutoResponder && (
<SettingOption
onPress={onPressHandler}
onPress={goToNotificationAutoResponder}
optionName='automatic_dm_replies'
/>
)}

View file

@ -285,3 +285,22 @@ export function filterProfilesMatchingTerm(users: UserProfile[], term: string):
some((suggestion) => suggestion.startsWith(trimmedTerm));
});
}
export function getNotificationProps(user: UserModel) {
if (user && user.notifyProps) {
return user.notifyProps;
}
const props: Partial<UserNotifyProps> = {
channel: 'true',
comments: 'any',
desktop: 'all',
desktop_sound: 'true',
first_name: (!user || !user.firstName) ? 'false' : 'true',
mention_keys: user ? `${user.username},@${user.username}` : '',
push: 'mention',
push_status: 'online',
};
return props;
}

View file

@ -441,8 +441,6 @@
"mobile.notice_mobile_link": "mobile apps",
"mobile.notice_platform_link": "server",
"mobile.notice_text": "Mattermost is made possible by the open source software used in our {platform} and {mobile}.",
"mobile.notification_settings.mobile": "Push Notifications",
"mobile.notification_settings.ooo_auto_responder": "Automatic Direct Message Replies",
"mobile.oauth.failed_to_login": "Your login attempt failed. Please try again.",
"mobile.oauth.failed_to_open_link": "The link failed to open. Please try again.",
"mobile.oauth.failed_to_open_link_no_browser": "The link failed to open. Please verify that a browser is installed on the device.",
@ -553,6 +551,11 @@
"more_messages.text": "{count} new {count, plural, one {message} other {messages}}",
"msg_typing.areTyping": "{users} and {last} are typing...",
"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.message_placeholder": "Message",
"notification_settings.mention.reply": "Send reply notifications for",
"notification_settings.mentions": "Mentions",
"notification_settings.mentions_replies": "Mentions and Replies",
@ -562,6 +565,21 @@
"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.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.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.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",

View file

@ -4,17 +4,17 @@
type UserNotifyProps = {
auto_responder_active?: 'true' | 'false';
auto_responder_message?: string;
channel: 'true' | 'false';
comments: 'never' | 'root' | 'any';
desktop: 'default' | 'all' | 'mention' | 'none';
desktop_notification_sound?: string;
desktop_sound: 'true' | 'false';
email: 'true' | 'false';
first_name: 'true' | 'false';
mark_unread: 'all' | 'mention';
mention_keys: string;
push: 'default' | 'all' | 'mention' | 'none';
push_status: 'ooo' | 'offline' | 'away' | 'dnd' | 'online';
comments: 'never' | 'root' | 'any';
first_name: 'true' | 'false';
channel: 'true' | 'false';
mention_keys: string;
user_id?: string;
};

4
types/screens/settings.d.ts vendored Normal file
View file

@ -0,0 +1,4 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
type PushStatus = 'away' | 'online' | 'offline' | 'none' | 'mention' | 'all';