MM-39711 - Gekidou Notification Mention main screen (#6309)

* 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

* adding text to those two components

* correction following review
This commit is contained in:
Avinash Lingaloo 2022-05-26 17:25:03 +04:00 committed by GitHub
parent 37457e3f31
commit b5038ba55e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 460 additions and 38 deletions

View file

@ -3,7 +3,7 @@
import {MessageDescriptor} from '@formatjs/intl/src/types';
import React from 'react';
import {StyleProp, View, ViewStyle} from 'react-native';
import {StyleProp, TextStyle, View, ViewStyle} from 'react-native';
import FormattedText from '@components/formatted_text';
import {useTheme} from '@context/theme';
@ -49,37 +49,41 @@ type SectionProps = {
footerText?: SectionText;
headerText: SectionText;
containerStyles?: StyleProp<ViewStyle>;
headerStyles?: StyleProp<TextStyle>;
footerStyles?: StyleProp<TextStyle>;
}
const Section = ({
const Block = ({
children,
containerStyles,
disableFooter,
disableHeader,
footerText,
headerStyles,
headerText,
footerStyles,
}: SectionProps) => {
const theme = useTheme();
const style = getStyleSheet(theme);
const styles = getStyleSheet(theme);
return (
<View style={style.container}>
<View style={styles.container}>
{(headerText && !disableHeader) &&
<FormattedText
defaultMessage={headerText.defaultMessage}
id={headerText.id}
style={style.header}
values={headerText.values}
style={[styles.header, headerStyles]}
/>
}
<View style={[style.items, containerStyles]}>
<View style={[styles.items, containerStyles]}>
{children}
</View>
{(footerText && !disableFooter) &&
<FormattedText
defaultMessage={footerText.defaultMessage}
id={footerText.id}
style={style.footer}
style={[styles.footer, footerStyles]}
values={footerText.values}
/>
}
@ -87,4 +91,4 @@ const Section = ({
);
};
export default Section;
export default Block;

View file

@ -69,7 +69,7 @@ type Props = {
testID?: string;
}
const SectionItem = ({testID = 'sectionItem', action, actionType, actionValue, label, selected, description, icon}: Props) => {
const BlockItem = ({testID = 'sectionItem', action, actionType, actionValue, label, selected, description, icon}: Props) => {
const theme = useTheme();
const style = getStyleSheet(theme);
@ -154,4 +154,4 @@ const SectionItem = ({testID = 'sectionItem', action, actionType, actionValue, l
return component;
};
export default SectionItem;
export default BlockItem;

View file

@ -8,15 +8,14 @@ export const BOTTOM_SHEET = 'BottomSheet';
export const BROWSE_CHANNELS = 'BrowseChannels';
export const CHANNEL = 'Channel';
export const CHANNEL_ADD_PEOPLE = 'ChannelAddPeople';
export const CHANNEL_INFO = 'ChannelInfo';
export const CHANNEL_EDIT = 'ChannelEdit';
export const CHANNEL_INFO = 'ChannelInfo';
export const CODE = 'Code';
export const CREATE_DIRECT_MESSAGE = 'CreateDirectMessage';
export const CREATE_OR_EDIT_CHANNEL = 'CreateOrEditChannel';
export const CREATE_TEAM = 'CreateTeam';
export const CUSTOM_STATUS = 'CustomStatus';
export const CUSTOM_STATUS_CLEAR_AFTER = 'CustomStatusClearAfter';
export const DISPLAY_SETTINGS = 'DisplaySettings';
export const EDIT_POST = 'EditPost';
export const EDIT_PROFILE = 'EditProfile';
export const EDIT_SERVER = 'EditServer';
@ -33,7 +32,6 @@ export const LATEX = 'Latex';
export const LOGIN = 'Login';
export const MENTIONS = 'Mentions';
export const MFA = 'MFA';
export const NOTIFICATION_SETTINGS = 'NotificationSettings';
export const PERMALINK = 'Permalink';
export const POST_OPTIONS = 'PostOptions';
export const REACTIONS = 'Reactions';
@ -42,6 +40,9 @@ export const SEARCH = 'Search';
export const SELECT_TEAM = 'SelectTeam';
export const SERVER = 'Server';
export const SETTINGS = 'Settings';
export const SETTINGS_DISPLAY = 'SettingsDisplay';
export const SETTINGS_NOTIFICATION = 'SettingsNotification';
export const SETTINGS_NOTIFICATION_MENTION = 'SettingsNotificationMention';
export const SNACK_BAR = 'SnackBar';
export const SSO = 'SSO';
export const THREAD = 'Thread';
@ -65,7 +66,6 @@ export default {
CREATE_TEAM,
CUSTOM_STATUS,
CUSTOM_STATUS_CLEAR_AFTER,
DISPLAY_SETTINGS,
EDIT_POST,
EDIT_PROFILE,
EDIT_SERVER,
@ -82,7 +82,6 @@ export default {
LOGIN,
MENTIONS,
MFA,
NOTIFICATION_SETTINGS,
PERMALINK,
POST_OPTIONS,
REACTIONS,
@ -91,6 +90,9 @@ export default {
SELECT_TEAM,
SERVER,
SETTINGS,
SETTINGS_DISPLAY,
SETTINGS_NOTIFICATION,
SETTINGS_NOTIFICATION_MENTION,
SNACK_BAR,
SSO,
THREAD,

View file

@ -17,11 +17,11 @@ import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
import {SafeAreaView} from 'react-native-safe-area-context';
import Autocomplete from '@components/autocomplete';
import BlockItem from '@components/block_item';
import ErrorText from '@components/error_text';
import FloatingTextInput from '@components/floating_text_input_label';
import FormattedText from '@components/formatted_text';
import Loading from '@components/loading';
import SectionItem from '@components/section_item';
import {General, Channel} from '@constants';
import {useTheme} from '@context/theme';
import {useIsTablet} from '@hooks/device';
@ -246,7 +246,7 @@ export default function ChannelInfoForm({
>
<View>
{showSelector && (
<SectionItem
<BlockItem
testID='channel_info_form.make_private'
label={makePrivateLabel}
description={makePrivateDescription}

View file

@ -185,11 +185,14 @@ Navigation.setLazyComponentRegistrator((screenName) => {
case Screens.SETTINGS:
screen = withServerDatabase(require('@screens/settings').default);
break;
case Screens.NOTIFICATION_SETTINGS:
case Screens.SETTINGS_DISPLAY:
screen = withServerDatabase(require('@screens/settings/display').default);
break;
case Screens.SETTINGS_NOTIFICATION:
screen = withServerDatabase(require('@screens/settings/notifications').default);
break;
case Screens.DISPLAY_SETTINGS:
screen = withServerDatabase(require('@screens/settings/display').default);
case Screens.SETTINGS_NOTIFICATION_MENTION:
screen = withServerDatabase(require('@screens/settings/notification_mention').default);
break;
}

View file

@ -39,7 +39,7 @@ export const SettingOptionConfig = {
export const NotificationsOptionConfig = {
mentions: {
iconName: 'at',
testID: 'notification_settings.mentions_replies.action',
testID: 'notification_settings.mentions_replies',
},
push_notification: {
defaultMessage: 'Push Notifications',

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 {observeIsCRTEnabled} from '@queries/servers/thread';
import {observeCurrentUser} from '@queries/servers/user';
import {WithDatabaseArgs} from '@typings/database/database';
import Mentions from './notification_mention';
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
return {
isCRTEnabled: observeIsCRTEnabled(database),
currentUser: observeCurrentUser(database),
};
});
export default withDatabase(enhanced(Mentions));

View file

@ -0,0 +1,198 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useReducer} from 'react';
import {Alert, Text, 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';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography';
const UPDATE_MENTION_PREF = 'UPDATE_MENTION_PREF';
const INITIAL_STATE = {
firstName: false,
usernameMention: false,
channel: false,
};
type Action = {
type: string;
data: Partial<typeof INITIAL_STATE>;
}
const reducer = (state: typeof INITIAL_STATE, action: Action) => {
switch (action.type) {
case UPDATE_MENTION_PREF:
return {
...state,
...action.data,
};
default:
return state;
}
};
const mentionHeaderText = {
id: t('notification_settings.mentions.wordsTrigger'),
defaultMessage: 'Words that trigger mentions',
};
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
return {
separator: {
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,
},
};
});
type MentionSectionProps = {
currentUser?: UserModel;
mentionKeys: string;
}
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 toggleChannelMentions = () => {
dispatch({
type: UPDATE_MENTION_PREF,
data: {
channel: !channel,
},
});
};
const toggleUsernameMention = () => {
dispatch({
type: UPDATE_MENTION_PREF,
data: {
usernameMention: !usernameMention,
},
});
};
const toggleFirstNameMention = () => {
dispatch({
type: UPDATE_MENTION_PREF,
data: {
firstName: !firstName,
},
});
};
const goToNotificationSettingsMentionKeywords = () => {
return Alert.alert(
'The functionality you are trying to use has not yet been implemented.',
);
};
return (
<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'
selected={firstName}
/>
<View style={styles.separator}/>
</>
)
}
{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'
/>
)}
<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'
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'
/>
</Block>
);
};
export default MentionSettings;

View file

@ -0,0 +1,70 @@
// 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 {SafeAreaView} from 'react-native-safe-area-context';
import {useTheme} from '@context/theme';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
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,
fontSize: 12,
height: 40,
},
scrollView: {
flex: 1,
backgroundColor: changeOpacity(theme.centerChannelColor, 0.06),
},
scrollViewContent: {
paddingVertical: 35,
},
};
});
type NotificationMentionProps = {
isCRTEnabled?: boolean;
currentUser?: UserModel;
mentionKeys: string;
}
const NotificationMention = ({currentUser, mentionKeys, isCRTEnabled}: NotificationMentionProps) => {
const theme = useTheme();
const styles = getStyleSheet(theme);
return (
<SafeAreaView
edges={['left', 'right']}
testID='notification_settings.screen'
style={styles.container}
>
<ScrollView
style={styles.scrollView}
contentContainerStyle={styles.scrollViewContent}
alwaysBounceVertical={false}
>
<MentionSettings
mentionKeys={mentionKeys}
currentUser={currentUser}
/>
{!isCRTEnabled && (
<ReplySettings/>
)}
</ScrollView>
</SafeAreaView>
);
};
export default NotificationMention;

View file

@ -0,0 +1,103 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useState} 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 replyHeaderText = {
id: t('notification_settings.mention.reply'),
defaultMessage: 'Send reply notifications for',
};
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,
},
};
});
const ReplySettings = () => {
const [replyNotificationType, setReplyNotificationType] = useState('any'); //todo: initialize with value from db/api
const theme = useTheme();
const styles = getStyleSheet(theme);
const setReplyNotifications = (notifType: string) => {
setReplyNotificationType(notifType);
};
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'
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'
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'
selected={replyNotificationType === 'never'}
/>
</Block>
);
};
export default ReplySettings;

View file

@ -11,11 +11,9 @@ import {WithDatabaseArgs} from '@typings/database/database';
import NotificationSettings from './notifications';
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
const isCRTEnabled = observeIsCRTEnabled(database);
const enableAutoResponder = observeConfigBooleanValue(database, 'ExperimentalEnableAutomaticReplies');
return {
isCRTEnabled,
enableAutoResponder,
isCRTEnabled: observeIsCRTEnabled(database),
enableAutoResponder: observeConfigBooleanValue(database, 'ExperimentalEnableAutomaticReplies'),
};
});

View file

@ -2,12 +2,15 @@
// See LICENSE.txt for license information.
import React from 'react';
import {useIntl} from 'react-intl';
import {Alert, Platform, ScrollView, View} from 'react-native';
import {Edge, SafeAreaView} from 'react-native-safe-area-context';
import {changeOpacity, makeStyleSheetFromTheme} from '@app/utils/theme';
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';
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
@ -41,11 +44,12 @@ type NotificationsProps = {
const Notifications = ({isCRTEnabled, enableAutoResponder}: NotificationsProps) => {
const theme = useTheme();
const styles = getStyleSheet(theme);
const intl = useIntl();
let mentionsI18nId = t('mobile.notification_settings.mentions_replies');
let mentionsI18nId = t('notification_settings.mentions_replies');
let mentionsI18nDefault = 'Mentions and Replies';
if (isCRTEnabled) {
mentionsI18nId = t('mobile.notification_settings.mentions');
mentionsI18nId = t('notification_settings.mentions');
mentionsI18nDefault = 'Mentions';
}
@ -55,6 +59,16 @@ const Notifications = ({isCRTEnabled, enableAutoResponder}: NotificationsProps)
);
};
const goToNotificationSettingsMentions = () => {
const screen = Screens.SETTINGS_NOTIFICATION_MENTION;
const id = isCRTEnabled ? t('notification_settings.mentions') : t('notification_settings.mentions_replies');
const defaultMessage = isCRTEnabled ? 'Mentions' : 'Mentions and Replies';
const title = intl.formatMessage({id, defaultMessage});
goToScreen(screen, title);
};
return (
<SafeAreaView
edges={edges}
@ -69,7 +83,7 @@ const Notifications = ({isCRTEnabled, enableAutoResponder}: NotificationsProps)
<SettingOption
defaultMessage={mentionsI18nDefault}
i18nId={mentionsI18nId}
onPress={onPressHandler}
onPress={goToNotificationSettingsMentions}
optionName='mentions'
/>
<SettingOption

View file

@ -122,22 +122,22 @@ const Settings = ({componentId, showHelp, siteName}: SettingsProps) => {
};
const goToNotifications = preventDoubleTap(() => {
const screen = Screens.NOTIFICATION_SETTINGS;
const title = intl.formatMessage({id: 'user.settings.notifications', defaultMessage: 'Notifications'});
const screen = Screens.SETTINGS_NOTIFICATION;
const title = intl.formatMessage({id: 'settings.notifications', defaultMessage: 'Notifications'});
goToScreen(screen, title);
});
const goToDisplaySettings = preventDoubleTap(() => {
const screen = Screens.DISPLAY_SETTINGS;
const title = intl.formatMessage({id: 'user.settings.display', defaultMessage: 'Display'});
const screen = Screens.SETTINGS_DISPLAY;
const title = intl.formatMessage({id: 'settings.display', defaultMessage: 'Display'});
goToScreen(screen, title);
});
const goToAbout = preventDoubleTap(() => {
const screen = Screens.ABOUT;
const title = intl.formatMessage({id: 'about.title', defaultMessage: 'About {appTitle}'}, {appTitle: serverName});
const title = intl.formatMessage({id: 'settings.about', defaultMessage: 'About {appTitle}'}, {appTitle: serverName});
goToScreen(screen, title);
});

View file

@ -9,7 +9,6 @@
"about.teamEditionSt": "All your team communication in one place, instantly searchable and accessible anywhere.",
"about.teamEditiont0": "Team Edition",
"about.teamEditiont1": "Enterprise Edition",
"about.title": "About {appTitle}",
"account.logout": "Log out",
"account.logout_from": "Log out of {serverName}",
"account.saved_messages": "Saved Messages",
@ -409,8 +408,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.mentions": "Mentions",
"mobile.notification_settings.mentions_replies": "Mentions and Replies",
"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.",
@ -523,6 +520,18 @@
"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.mention.reply": "Send reply notifications for",
"notification_settings.mentions": "Mentions",
"notification_settings.mentions_replies": "Mentions and Replies",
"notification_settings.mentions.channelWide": "Channel-wide mentions",
"notification_settings.mentions.keywords": "Keywords",
"notification_settings.mentions.keywordsDescription": "Other words that trigger a mention",
"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.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",
"notification.message_not_found": "Message not found",
"notification.not_channel_member": "This message belongs to a channel where you are not a member.",
"notification.not_team_member": "This message belongs to a team where you are not a member.",
@ -586,6 +595,9 @@
"servers.login": "Log in",
"servers.logout": "Log out",
"servers.remove": "Remove",
"settings.about": "About {appTitle}",
"settings.display": "Display",
"settings.notifications": "Notifications",
"snack.bar.favorited.channel": "This channel was favorited",
"snack.bar.link.copied": "Link copied to clipboard",
"snack.bar.message.copied": "Text copied to clipboard",
@ -632,7 +644,6 @@
"threads.unfollowThread": "Unfollow Thread",
"user.edit_profile.email.auth_service": "Login occurs through {service}. Email cannot be updated. Email address used for notifications is {email}.",
"user.edit_profile.email.web_client": "Email must be updated using a web client or desktop application.",
"user.settings.display": "Display",
"user.settings.general.email": "Email",
"user.settings.general.field_handled_externally": "Some fields below are handled through your login provider. If you want to change them, youll need to do so through your login provider.",
"user.settings.general.firstName": "First Name",
@ -640,7 +651,6 @@
"user.settings.general.nickname": "Nickname",
"user.settings.general.position": "Position",
"user.settings.general.username": "Username",
"user.settings.notifications": "Notifications",
"video.download": "Download video",
"video.failed_description": "An error occurred while trying to play the video.\n",
"video.failed_title": "Video playback failed",