From 2fc1386b78530128312cfb0be55900e3ab35b746 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Fri, 24 Feb 2023 12:41:36 +0200 Subject: [PATCH] feat: Channel notification preferences (#7160) * feat: Channel notification preferences * feedback review * use button color for the icon --- app/components/block/index.tsx | 88 -------------- app/components/button/index.tsx | 36 ++++-- app/components/channel_item/index.ts | 7 +- app/components/settings/block.tsx | 91 +++++++++++++++ .../settings/container.tsx} | 0 .../settings/item.tsx} | 5 +- .../settings/option.tsx} | 0 .../settings/separator.tsx} | 0 app/constants/screens.ts | 5 +- app/queries/servers/channel.ts | 4 + app/screens/channel_info/options/index.tsx | 3 +- .../options/notification_preference/index.ts | 8 +- .../notification_preference.tsx | 85 +++++++++----- .../channel_notification_preferences.tsx | 108 ++++++++++++++++++ .../channel_notification_preferences/index.ts | 53 +++++++++ .../muted_banner.tsx | 102 +++++++++++++++++ .../notify_about.tsx | 93 +++++++++++++++ .../reset.tsx | 62 ++++++++++ .../thread_replies.tsx | 57 +++++++++ app/screens/index.tsx | 3 + app/screens/settings/about/about.tsx | 4 +- app/screens/settings/advanced/index.tsx | 7 +- app/screens/settings/display/display.tsx | 5 +- .../settings/display_clock/display_clock.tsx | 9 +- .../settings/display_crt/display_crt.tsx | 9 +- .../settings/display_theme/custom_theme.tsx | 5 +- .../settings/display_theme/display_theme.tsx | 3 +- .../display_timezone/display_timezone.tsx | 7 +- .../display_timezone_select/timezone_row.tsx | 2 +- .../notification_auto_responder.tsx | 7 +- .../notification_email/notification_email.tsx | 9 +- .../notification_mention/mention_settings.tsx | 7 +- .../notification_mention.tsx | 2 +- .../notification_mention/reply_settings.tsx | 7 +- .../notification_push/notification_push.tsx | 5 +- .../settings/notification_push/push_send.tsx | 7 +- .../notification_push/push_status.tsx | 7 +- .../notification_push/push_thread.tsx | 7 +- .../settings/notifications/notifications.tsx | 5 +- .../report_problem/report_problem.tsx | 3 +- app/screens/settings/setting_block.tsx | 46 -------- app/screens/settings/settings.tsx | 4 +- app/utils/user/index.ts | 2 +- assets/base/i18n/en.json | 11 ++ index.ts | 5 +- types/api/channels.d.ts | 1 + 46 files changed, 744 insertions(+), 252 deletions(-) delete mode 100644 app/components/block/index.tsx create mode 100644 app/components/settings/block.tsx rename app/{screens/settings/setting_container.tsx => components/settings/container.tsx} (100%) rename app/{screens/settings/setting_item.tsx => components/settings/item.tsx} (95%) rename app/{screens/settings/setting_option.tsx => components/settings/option.tsx} (100%) rename app/{screens/settings/settings_separator.tsx => components/settings/separator.tsx} (100%) create mode 100644 app/screens/channel_notification_preferences/channel_notification_preferences.tsx create mode 100644 app/screens/channel_notification_preferences/index.ts create mode 100644 app/screens/channel_notification_preferences/muted_banner.tsx create mode 100644 app/screens/channel_notification_preferences/notify_about.tsx create mode 100644 app/screens/channel_notification_preferences/reset.tsx create mode 100644 app/screens/channel_notification_preferences/thread_replies.tsx delete mode 100644 app/screens/settings/setting_block.tsx diff --git a/app/components/block/index.tsx b/app/components/block/index.tsx deleted file mode 100644 index ea1fd0c48e..0000000000 --- a/app/components/block/index.tsx +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; -import {StyleProp, TextStyle, View, ViewStyle} from 'react-native'; - -import FormattedText from '@components/formatted_text'; -import {useTheme} from '@context/theme'; -import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; - -import type {MessageDescriptor} from '@formatjs/intl/src/types'; - -const getStyleSheet = makeStyleSheetFromTheme((theme) => { - return { - container: { - marginBottom: 30, - }, - header: { - marginHorizontal: 15, - marginBottom: 10, - fontSize: 13, - color: changeOpacity(theme.centerChannelColor, 0.5), - }, - footer: { - marginTop: 10, - marginHorizontal: 15, - fontSize: 12, - color: changeOpacity(theme.centerChannelColor, 0.5), - }, - }; -}); - -export type SectionText = { - id: string; - defaultMessage: string; - values?: MessageDescriptor; -} - -export type BlockProps = { - children: React.ReactNode; - disableFooter?: boolean; - disableHeader?: boolean; - footerText?: SectionText; - headerText?: SectionText; - containerStyles?: StyleProp; - headerStyles?: StyleProp; - footerStyles?: StyleProp; -} - -const Block = ({ - children, - containerStyles, - disableFooter, - disableHeader, - footerText, - headerStyles, - headerText, - footerStyles, -}: BlockProps) => { - const theme = useTheme(); - const styles = getStyleSheet(theme); - - return ( - - {(headerText && !disableHeader) && - - } - - {children} - - {(footerText && !disableFooter) && - - } - - ); -}; - -export default Block; diff --git a/app/components/button/index.tsx b/app/components/button/index.tsx index 24c4c0a172..317a7c31c6 100644 --- a/app/components/button/index.tsx +++ b/app/components/button/index.tsx @@ -2,12 +2,15 @@ // See LICENSE.txt for license information. import React, {useMemo} from 'react'; -import {StyleProp, Text, TextStyle, ViewStyle} from 'react-native'; +import {StyleProp, StyleSheet, Text, TextStyle, View, ViewStyle} from 'react-native'; import RNButton from 'react-native-button'; +import CompassIcon from '@components/compass_icon'; import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles'; -type Props = { +type ConditionalProps = | {iconName: string; iconSize: number} | {iconName?: never; iconSize?: never} + +type Props = ConditionalProps & { theme: Theme; backgroundStyle?: StyleProp; textStyle?: StyleProp; @@ -20,6 +23,11 @@ type Props = { text: string; } +const styles = StyleSheet.create({ + container: {flexDirection: 'row'}, + icon: {marginRight: 7}, +}); + const Button = ({ theme, backgroundStyle, @@ -31,6 +39,8 @@ const Button = ({ onPress, text, testID, + iconName, + iconSize, }: Props) => { const bgStyle = useMemo(() => [ buttonBackgroundStyle(theme, size, emphasis, buttonType, buttonState), @@ -48,12 +58,22 @@ const Button = ({ onPress={onPress} testID={testID} > - - {text} - + + {Boolean(iconName) && + + } + + {text} + + ); }; diff --git a/app/components/channel_item/index.ts b/app/components/channel_item/index.ts index 0e881b2f42..9e07bf3404 100644 --- a/app/components/channel_item/index.ts +++ b/app/components/channel_item/index.ts @@ -10,7 +10,7 @@ import {switchMap, distinctUntilChanged} from 'rxjs/operators'; import {observeChannelsWithCalls} from '@calls/state'; import {General} from '@constants'; import {withServerUrl} from '@context/server'; -import {observeChannelSettings, observeMyChannel, queryChannelMembers} from '@queries/servers/channel'; +import {observeIsMutedSetting, observeMyChannel, queryChannelMembers} from '@queries/servers/channel'; import {queryDraft} from '@queries/servers/drafts'; import {observeCurrentChannelId, observeCurrentUserId} from '@queries/servers/system'; import {observeTeam} from '@queries/servers/team'; @@ -19,7 +19,6 @@ import ChannelItem from './channel_item'; import type {WithDatabaseArgs} from '@typings/database/database'; import type ChannelModel from '@typings/database/models/servers/channel'; -import type MyChannelModel from '@typings/database/models/servers/my_channel'; type EnhanceProps = WithDatabaseArgs & { channel: ChannelModel; @@ -27,8 +26,6 @@ type EnhanceProps = WithDatabaseArgs & { serverUrl?: string; } -const observeIsMutedSetting = (mc: MyChannelModel) => observeChannelSettings(mc.database, mc.id).pipe(switchMap((s) => of$(s?.notifyProps?.mark_unread === General.MENTION))); - const enhance = withObservables(['channel', 'showTeamName'], ({ channel, database, @@ -53,7 +50,7 @@ const enhance = withObservables(['channel', 'showTeamName'], ({ if (!mc) { return of$(false); } - return observeIsMutedSetting(mc); + return observeIsMutedSetting(database, mc.id); }), ); diff --git a/app/components/settings/block.tsx b/app/components/settings/block.tsx new file mode 100644 index 0000000000..c19482c19e --- /dev/null +++ b/app/components/settings/block.tsx @@ -0,0 +1,91 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +import React from 'react'; +import {LayoutChangeEvent, StyleProp, TextStyle, View, ViewStyle} from 'react-native'; + +import FormattedText from '@components/formatted_text'; +import {useTheme} from '@context/theme'; +import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; +import {typography} from '@utils/typography'; + +import type {MessageDescriptor} from 'react-intl'; + +type SectionText = { + id: string; + defaultMessage: string; + values?: MessageDescriptor; +} + +type SettingBlockProps = { + children: React.ReactNode; + containerStyles?: StyleProp; + disableFooter?: boolean; + disableHeader?: boolean; + footerStyles?: StyleProp; + footerText?: SectionText; + headerStyles?: StyleProp; + headerText?: SectionText; + onLayout?: (event: LayoutChangeEvent) => void; +}; + +const getStyleSheet = makeStyleSheetFromTheme((theme) => { + return { + container: { + marginBottom: 30, + }, + contentContainerStyle: { + marginBottom: 0, + }, + header: { + color: theme.centerChannelColor, + ...typography('Heading', 300, 'SemiBold'), + marginBottom: 8, + marginLeft: 20, + marginTop: 12, + marginRight: 15, + }, + footer: { + marginTop: 10, + marginHorizontal: 15, + fontSize: 12, + color: changeOpacity(theme.centerChannelColor, 0.5), + }, + }; +}); + +const SettingBlock = ({ + children, containerStyles, disableFooter, disableHeader, + footerStyles, footerText, headerStyles, headerText, onLayout, +}: SettingBlockProps) => { + const theme = useTheme(); + const styles = getStyleSheet(theme); + + return ( + + {(headerText && !disableHeader) && + + } + + {children} + + {(footerText && !disableFooter) && + + } + + ); +}; + +export default SettingBlock; diff --git a/app/screens/settings/setting_container.tsx b/app/components/settings/container.tsx similarity index 100% rename from app/screens/settings/setting_container.tsx rename to app/components/settings/container.tsx diff --git a/app/screens/settings/setting_item.tsx b/app/components/settings/item.tsx similarity index 95% rename from app/screens/settings/setting_item.tsx rename to app/components/settings/item.tsx index 8fcb10fe9a..dea88d7f32 100644 --- a/app/screens/settings/setting_item.tsx +++ b/app/components/settings/item.tsx @@ -7,11 +7,12 @@ import {Platform} from 'react-native'; import OptionItem, {OptionItemProps} from '@components/option_item'; import {useTheme} from '@context/theme'; -import SettingSeparator from '@screens/settings/settings_separator'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import {typography} from '@utils/typography'; -import Options, {DisplayOptionConfig, NotificationsOptionConfig, SettingOptionConfig} from './config'; +import Options, {DisplayOptionConfig, NotificationsOptionConfig, SettingOptionConfig} from '../../screens/settings/config'; + +import SettingSeparator from './separator'; type SettingsConfig = keyof typeof SettingOptionConfig | keyof typeof NotificationsOptionConfig| keyof typeof DisplayOptionConfig type SettingOptionProps = { diff --git a/app/screens/settings/setting_option.tsx b/app/components/settings/option.tsx similarity index 100% rename from app/screens/settings/setting_option.tsx rename to app/components/settings/option.tsx diff --git a/app/screens/settings/settings_separator.tsx b/app/components/settings/separator.tsx similarity index 100% rename from app/screens/settings/settings_separator.tsx rename to app/components/settings/separator.tsx diff --git a/app/constants/screens.ts b/app/constants/screens.ts index 6880459e5f..e5a33c0b52 100644 --- a/app/constants/screens.ts +++ b/app/constants/screens.ts @@ -10,7 +10,7 @@ export const CALL = 'Call'; export const CHANNEL = 'Channel'; export const CHANNEL_ADD_PEOPLE = 'ChannelAddPeople'; export const CHANNEL_INFO = 'ChannelInfo'; -export const CHANNEL_MENTION = 'ChannelMention'; +export const CHANNEL_NOTIFICATION_PREFERENCES = 'ChannelNotificationPreferences'; export const CODE = 'Code'; export const CREATE_DIRECT_MESSAGE = 'CreateDirectMessage'; export const CREATE_OR_EDIT_CHANNEL = 'CreateOrEditChannel'; @@ -79,7 +79,7 @@ export default { CHANNEL, CHANNEL_ADD_PEOPLE, CHANNEL_INFO, - CHANNEL_MENTION, + CHANNEL_NOTIFICATION_PREFERENCES, CODE, CREATE_DIRECT_MESSAGE, CREATE_OR_EDIT_CHANNEL, @@ -172,6 +172,5 @@ export const SCREENS_AS_BOTTOM_SHEET = new Set([ export const NOT_READY = [ CHANNEL_ADD_PEOPLE, - CHANNEL_MENTION, CREATE_TEAM, ]; diff --git a/app/queries/servers/channel.ts b/app/queries/servers/channel.ts index 1acabd0f4c..d32accfce8 100644 --- a/app/queries/servers/channel.ts +++ b/app/queries/servers/channel.ts @@ -628,6 +628,10 @@ export const observeChannelSettings = (database: Database, channelId: string) => ); }; +export const observeIsMutedSetting = (database: Database, channelId: string) => { + return observeChannelSettings(database, channelId).pipe(switchMap((s) => of$(s?.notifyProps?.mark_unread === General.MENTION))); +}; + export const observeChannelsByLastPostAt = (database: Database, myChannels: MyChannelModel[], excludeIds?: string[]) => { const ids = myChannels.map((c) => c.id); const idsStr = `'${ids.join("','")}'`; diff --git a/app/screens/channel_info/options/index.tsx b/app/screens/channel_info/options/index.tsx index d2c856ec6f..e045d066df 100644 --- a/app/screens/channel_info/options/index.tsx +++ b/app/screens/channel_info/options/index.tsx @@ -10,6 +10,7 @@ import {isTypeDMorGM} from '@utils/channel'; import EditChannel from './edit_channel'; import IgnoreMentions from './ignore_mentions'; import Members from './members'; +import NotificationPreference from './notification_preference'; import PinnedMessages from './pinned_messages'; type Props = { @@ -26,7 +27,7 @@ const Options = ({channelId, type, callsEnabled}: Props) => { {type !== General.DM_CHANNEL && } - {/**/} + {type !== General.DM_CHANNEL && diff --git a/app/screens/channel_info/options/notification_preference/index.ts b/app/screens/channel_info/options/notification_preference/index.ts index 77f52b75c1..48bd52b3f7 100644 --- a/app/screens/channel_info/options/notification_preference/index.ts +++ b/app/screens/channel_info/options/notification_preference/index.ts @@ -6,7 +6,9 @@ import withObservables from '@nozbe/with-observables'; import {of as of$} from 'rxjs'; import {switchMap} from 'rxjs/operators'; -import {observeChannelSettings} from '@queries/servers/channel'; +import {observeChannel, observeChannelSettings} from '@queries/servers/channel'; +import {observeCurrentUser} from '@queries/servers/user'; +import {getNotificationProps} from '@utils/user'; import NotificationPreference from './notification_preference'; @@ -17,13 +19,17 @@ type Props = WithDatabaseArgs & { } const enhanced = withObservables(['channelId'], ({channelId, database}: Props) => { + const displayName = observeChannel(database, channelId).pipe(switchMap((c) => of$(c?.displayName))); const settings = observeChannelSettings(database, channelId); + const userNotifyLevel = observeCurrentUser(database).pipe(switchMap((u) => of$(getNotificationProps(u).push))); const notifyLevel = settings.pipe( switchMap((s) => of$(s?.notifyProps.push)), ); return { + displayName, notifyLevel, + userNotifyLevel, }; }); diff --git a/app/screens/channel_info/options/notification_preference/notification_preference.tsx b/app/screens/channel_info/options/notification_preference/notification_preference.tsx index f8b3991a8d..83fdf7c423 100644 --- a/app/screens/channel_info/options/notification_preference/notification_preference.tsx +++ b/app/screens/channel_info/options/notification_preference/notification_preference.tsx @@ -7,54 +7,85 @@ import {Platform} from 'react-native'; import OptionItem from '@components/option_item'; import {NotificationLevel, Screens} from '@constants'; +import {useTheme} from '@context/theme'; import {t} from '@i18n'; import {goToScreen} from '@screens/navigation'; import {preventDoubleTap} from '@utils/tap'; +import {changeOpacity} from '@utils/theme'; + +import type {Options} from 'react-native-navigation'; type Props = { channelId: string; + displayName: string; notifyLevel: NotificationLevel; + userNotifyLevel: NotificationLevel; } -const NotificationPreference = ({channelId, notifyLevel}: Props) => { +const notificationLevel = (notifyLevel: NotificationLevel) => { + let id = ''; + let defaultMessage = ''; + switch (notifyLevel) { + case NotificationLevel.ALL: { + id = t('channel_info.notification.all'); + defaultMessage = 'All'; + break; + } + case NotificationLevel.MENTION: { + id = t('channel_info.notification.mention'); + defaultMessage = 'Mentions'; + break; + } + case NotificationLevel.NONE: { + id = t('channel_info.notification.none'); + defaultMessage = 'Never'; + break; + } + default: + id = t('channel_info.notification.default'); + defaultMessage = 'Default'; + break; + } + + return {id, defaultMessage}; +}; + +const NotificationPreference = ({channelId, displayName, notifyLevel, userNotifyLevel}: Props) => { const {formatMessage} = useIntl(); + const theme = useTheme(); const title = formatMessage({id: 'channel_info.mobile_notifications', defaultMessage: 'Mobile Notifications'}); - const goToMentions = preventDoubleTap(() => { - goToScreen(Screens.CHANNEL_MENTION, title, {channelId}); + const goToChannelNotificationPreferences = preventDoubleTap(() => { + const options: Options = { + topBar: { + title: { + text: title, + }, + subtitle: { + color: changeOpacity(theme.sidebarHeaderTextColor, 0.72), + text: displayName, + }, + backButton: { + popStackOnPress: false, + }, + }, + }; + goToScreen(Screens.CHANNEL_NOTIFICATION_PREFERENCES, title, {channelId}, options); }); const notificationLevelToText = () => { - let id = ''; - let defaultMessage = ''; - switch (notifyLevel) { - case NotificationLevel.ALL: { - id = t('channel_info.notification.all'); - defaultMessage = 'All'; - break; - } - case NotificationLevel.MENTION: { - id = t('channel_info.notification.mention'); - defaultMessage = 'Mentions'; - break; - } - case NotificationLevel.NONE: { - id = t('channel_info.notification.none'); - defaultMessage = 'Never'; - break; - } - default: - id = t('channel_info.notification.default'); - defaultMessage = 'Default'; - break; + if (notifyLevel === NotificationLevel.DEFAULT) { + const userLevel = notificationLevel(userNotifyLevel); + return formatMessage(userLevel); } - return formatMessage({id, defaultMessage}); + const channelLevel = notificationLevel(notifyLevel); + return formatMessage(channelLevel); }; return ( { + const serverUrl = useServerUrl(); + const defaultNotificationReplies = defaultThreadReplies === 'all'; + const diffNotificationLevel = notifyLevel !== 'default' && notifyLevel !== defaultLevel; + const notifyTitleTop = useSharedValue((isMuted ? MUTED_BANNER_HEIGHT : 0) + BLOCK_TITLE_HEIGHT); + const [notifyAbout, setNotifyAbout] = useState((notifyLevel === undefined || notifyLevel === 'default') ? defaultLevel : notifyLevel); + const [threadReplies, setThreadReplies] = useState((notifyThreadReplies || defaultThreadReplies) === 'all'); + const [resetDefaultVisible, setResetDefaultVisible] = useState(diffNotificationLevel || defaultNotificationReplies !== threadReplies); + + useDidUpdate(() => { + LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); + }, [isMuted]); + + const onResetPressed = useCallback(() => { + setResetDefaultVisible(false); + setNotifyAbout(defaultLevel); + setThreadReplies(defaultNotificationReplies); + }, [defaultLevel, defaultNotificationReplies]); + + const onNotificationLevel = useCallback((level: NotificationLevel) => { + setNotifyAbout(level); + setResetDefaultVisible(level !== defaultLevel || defaultNotificationReplies !== threadReplies); + }, [defaultLevel, defaultNotificationReplies, threadReplies]); + + const onSetThreadReplies = useCallback((value: boolean) => { + setThreadReplies(value); + setResetDefaultVisible(defaultNotificationReplies !== value || notifyAbout !== defaultLevel); + }, [defaultLevel, defaultNotificationReplies, notifyAbout]); + + const save = useCallback(() => { + const pushThreads = threadReplies ? 'all' : 'mention'; + + if (notifyLevel !== notifyAbout || (isCRTEnabled && pushThreads !== notifyThreadReplies)) { + const props: Partial = {push: notifyAbout}; + if (isCRTEnabled) { + props.push_threads = pushThreads; + } + + updateChannelNotifyProps(serverUrl, channelId, props); + } + popTopScreen(componentId); + }, [channelId, componentId, isCRTEnabled, notifyAbout, notifyLevel, notifyThreadReplies, serverUrl, threadReplies]); + + useBackNavigation(save); + useAndroidHardwareBackHandler(componentId, save); + + return ( + + {isMuted && } + {resetDefaultVisible && + + } + + {isCRTEnabled && + + } + + ); +}; + +export default ChannelNotificationPreferences; diff --git a/app/screens/channel_notification_preferences/index.ts b/app/screens/channel_notification_preferences/index.ts new file mode 100644 index 0000000000..d248c4549b --- /dev/null +++ b/app/screens/channel_notification_preferences/index.ts @@ -0,0 +1,53 @@ +// 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 {observeChannelSettings, observeIsMutedSetting} from '@queries/servers/channel'; +import {observeIsCRTEnabled} from '@queries/servers/thread'; +import {observeCurrentUser} from '@queries/servers/user'; +import {getNotificationProps} from '@utils/user'; + +import ChannelNotificationPreferences from './channel_notification_preferences'; + +import type {WithDatabaseArgs} from '@typings/database/database'; + +type EnhancedProps = WithDatabaseArgs & { + channelId: string; +} + +const enhanced = withObservables([], ({channelId, database}: EnhancedProps) => { + const settings = observeChannelSettings(database, channelId); + const isCRTEnabled = observeIsCRTEnabled(database); + const isMuted = observeIsMutedSetting(database, channelId); + const notifyProps = observeCurrentUser(database).pipe(switchMap((u) => of$(getNotificationProps(u)))); + + const notifyLevel = settings.pipe( + switchMap((s) => of$(s?.notifyProps.push)), + ); + + const notifyThreadReplies = settings.pipe( + switchMap((s) => of$(s?.notifyProps.push_threads)), + ); + + const defaultLevel = notifyProps.pipe( + switchMap((n) => of$(n?.push)), + ); + const defaultThreadReplies = notifyProps.pipe( + switchMap((n) => of$(n?.push_threads)), + ); + + return { + isCRTEnabled, + isMuted, + notifyLevel, + notifyThreadReplies, + defaultLevel, + defaultThreadReplies, + }; +}); + +export default withDatabase(enhanced(ChannelNotificationPreferences)); diff --git a/app/screens/channel_notification_preferences/muted_banner.tsx b/app/screens/channel_notification_preferences/muted_banner.tsx new file mode 100644 index 0000000000..0b7451ef4b --- /dev/null +++ b/app/screens/channel_notification_preferences/muted_banner.tsx @@ -0,0 +1,102 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React, {useCallback} from 'react'; +import {useIntl} from 'react-intl'; +import {View} from 'react-native'; +import Animated, {FlipOutXUp} from 'react-native-reanimated'; + +import {toggleMuteChannel} from '@actions/remote/channel'; +import Button from '@app/components/button'; +import CompassIcon from '@app/components/compass_icon'; +import FormattedText from '@app/components/formatted_text'; +import {useServerUrl} from '@app/context/server'; +import {useTheme} from '@app/context/theme'; +import {preventDoubleTap} from '@app/utils/tap'; +import {changeOpacity, makeStyleSheetFromTheme} from '@app/utils/theme'; +import {typography} from '@utils/typography'; + +type Props = { + channelId: string; +} + +export const MUTED_BANNER_HEIGHT = 200; + +const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ + button: {width: '55%'}, + container: { + backgroundColor: changeOpacity(theme.sidebarTextActiveBorder, 0.16), + borderRadius: 4, + marginHorizontal: 20, + marginVertical: 12, + paddingHorizontal: 16, + height: MUTED_BANNER_HEIGHT, + }, + contentText: { + ...typography('Body', 200), + color: theme.centerChannelColor, + marginTop: 12, + marginBottom: 16, + }, + titleContainer: { + alignItems: 'center', + flexDirection: 'row', + marginTop: 16, + }, + title: { + ...typography('Heading', 200), + color: theme.centerChannelColor, + marginLeft: 10, + paddingTop: 5, + }, +})); + +const MutedBanner = ({channelId}: Props) => { + const {formatMessage} = useIntl(); + const serverUrl = useServerUrl(); + const theme = useTheme(); + const styles = getStyleSheet(theme); + + const onPress = useCallback(preventDoubleTap(() => { + toggleMuteChannel(serverUrl, channelId, false); + }), [channelId, serverUrl]); + + return ( + + + + + + +