diff --git a/lib/shared/thread-settings-notifications-utils.js b/lib/shared/thread-settings-notifications-utils.js index 09decc906..ea9ff0dba 100644 --- a/lib/shared/thread-settings-notifications-utils.js +++ b/lib/shared/thread-settings-notifications-utils.js @@ -1,169 +1,169 @@ // @flow import * as React from 'react'; import { threadIsSidebar } from './thread-utils.js'; import { updateSubscriptionActionTypes, useUpdateSubscription, } from '../actions/user-actions.js'; import { useCanPromoteSidebar } from '../hooks/promote-sidebar.react.js'; import { createLoadingStatusSelector } from '../selectors/loading-selectors.js'; import { threadInfoSelector } from '../selectors/thread-selectors.js'; import type { ThreadInfo } from '../types/minimally-encoded-thread-permissions-types.js'; import { useDispatchActionPromise } from '../utils/redux-promise-utils.js'; import { useSelector } from '../utils/redux-utils.js'; type NotificationSettings = 'focused' | 'badge-only' | 'background'; const threadSettingsNotificationsCopy = { BANNER_NOTIFS: 'Banner notifs', - BADGE_COUNT: 'Badge count', + NOTIF_COUNT: 'Notif count', IN_FOCUSED_TAB: 'Lives in Focused tab', IN_BACKGROUND_TAB: 'Lives in Background tab', FOCUSED: 'Focused (enabled)', BADGE_ONLY: 'Focused (badge only)', BACKGROUND: 'Background', SIDEBAR_TITLE: 'Thread notifications', CHANNEL_TITLE: 'Channel notifications', IS_SIDEBAR: 'It’s not possible to move this thread to Background. ' + 'That’s because Comm’s design always shows threads ' + 'underneath their parent in the Inbox, which means ' + 'that if a thread’s parent is in Focused, the thread ' + 'must also be there.', IS_SIDEBAR_CAN_PROMOTE: 'If you want to move this thread to Background, ' + 'you can either move the parent to Background, ' + 'or you can promote the thread to a channel.', IS_SIDEBAR_CAN_NOT_PROMOTE: 'If you want to move this thread to Background, ' + 'you’ll have to move the parent to Background.', PARENT_THREAD_IS_BACKGROUND: 'It’s not possible to change the notif settings for a thread ' + 'whose parent is in Background. That’s because Comm’s design ' + 'always shows threads underneath their parent in the Inbox, ' + 'which means that if a thread’s parent is in Background, the ' + 'thread must also be there.', PARENT_THREAD_IS_BACKGROUND_CAN_PROMOTE: 'If you want to change the notif settings for this thread, ' + 'you can either change the notif settings for the parent, ' + 'or you can promote the thread to a channel.', PARENT_THREAD_IS_BACKGROUND_CAN_NOT_PROMOTE: 'If you want to change the notif settings for this thread, ' + 'you’ll have to change the notif settings for the parent.', }; const updateSubscriptionLoadingStatusSelector = createLoadingStatusSelector( updateSubscriptionActionTypes, ); function useThreadSettingsNotifications( threadInfo: ThreadInfo, onSuccessCallback: () => mixed, ): { +notificationSettings: NotificationSettings, +onFocusedSelected: () => mixed, +onBadgeOnlySelected: () => mixed, +onBackgroundSelected: () => mixed, +saveButtonDisabled: boolean, +onSave: () => mixed, +isSidebar: boolean, +canPromoteSidebar: boolean, +parentThreadIsInBackground: boolean, } { const subscription = threadInfo.currentUser.subscription; const initialThreadSetting = React.useMemo(() => { if (!subscription.home) { return 'background'; } if (!subscription.pushNotifs) { return 'badge-only'; } return 'focused'; }, [subscription.home, subscription.pushNotifs]); const [notificationSettings, setNotificationSettings] = React.useState(initialThreadSetting); const onFocusedSelected = React.useCallback( () => setNotificationSettings('focused'), [], ); const onBadgeOnlySelected = React.useCallback( () => setNotificationSettings('badge-only'), [], ); const onBackgroundSelected = React.useCallback( () => setNotificationSettings('background'), [], ); const dispatchActionPromise = useDispatchActionPromise(); const callUpdateSubscription = useUpdateSubscription(); const updateSubscriptionPromise = React.useCallback(async () => { const res = await callUpdateSubscription({ threadID: threadInfo.id, updatedFields: { home: notificationSettings !== 'background', pushNotifs: notificationSettings === 'focused', }, }); onSuccessCallback(); return res; }, [ callUpdateSubscription, notificationSettings, onSuccessCallback, threadInfo.id, ]); const updateSubscriptionLoadingStatus = useSelector( updateSubscriptionLoadingStatusSelector, ); const isLoading = updateSubscriptionLoadingStatus === 'loading'; const saveButtonDisabled = isLoading || notificationSettings === initialThreadSetting; const onSave = React.useCallback(() => { if (saveButtonDisabled) { return; } void dispatchActionPromise( updateSubscriptionActionTypes, updateSubscriptionPromise(), ); }, [saveButtonDisabled, dispatchActionPromise, updateSubscriptionPromise]); const isSidebar = threadIsSidebar(threadInfo); const { parentThreadID } = threadInfo; const parentThreadInfo = useSelector(state => parentThreadID ? threadInfoSelector(state)[parentThreadID] : null, ); const canPromoteSidebar = useCanPromoteSidebar(threadInfo, parentThreadInfo); const parentThreadIsInBackground = isSidebar && !parentThreadInfo?.currentUser.subscription.home; return { notificationSettings, onFocusedSelected, onBadgeOnlySelected, onBackgroundSelected, saveButtonDisabled, onSave, isSidebar, canPromoteSidebar, parentThreadIsInBackground, }; } export { threadSettingsNotificationsCopy, useThreadSettingsNotifications }; diff --git a/native/chat/settings/thread-settings-notifications.react.js b/native/chat/settings/thread-settings-notifications.react.js index 2ad2e2190..ff2013848 100644 --- a/native/chat/settings/thread-settings-notifications.react.js +++ b/native/chat/settings/thread-settings-notifications.react.js @@ -1,388 +1,388 @@ // @flow import * as React from 'react'; import { View, Text } from 'react-native'; import { threadSettingsNotificationsCopy, useThreadSettingsNotifications, } from 'lib/shared/thread-settings-notifications-utils.js'; import type { ThreadInfo } from 'lib/types/minimally-encoded-thread-permissions-types.js'; import EnumSettingsOption from '../../components/enum-settings-option.react.js'; import SWMansionIcon from '../../components/swmansion-icon.react.js'; import HeaderRightTextButton from '../../navigation/header-right-text-button.react.js'; import type { NavigationRoute } from '../../navigation/route-names.js'; import { useStyles, useColors } from '../../themes/colors.js'; import AllNotifsIllustration from '../../vectors/all-notifs-illustration.react.js'; import BadgeNotifsIllustration from '../../vectors/badge-notifs-illustration.react.js'; import MutedNotifsIllustration from '../../vectors/muted-notifs-illustration.react.js'; import type { ChatNavigationProp } from '../chat.react.js'; export type ThreadSettingsNotificationsParams = { +threadInfo: ThreadInfo, }; type NotificationDescriptionProps = { +selected: boolean, +bannerNotifsEnabled: boolean, - +badgeCountEnabled: boolean, + +notifCountEnabled: boolean, +livesInFocusedTab: boolean, }; function NotificationDescription( props: NotificationDescriptionProps, ): React.Node { const { selected, bannerNotifsEnabled, - badgeCountEnabled, + notifCountEnabled, livesInFocusedTab, } = props; const styles = useStyles(unboundStyles); const colors = useColors(); const bannerNotifsDescriptionTextStyles = React.useMemo(() => { const style = [styles.notificationOptionDescriptionText]; if (selected && !bannerNotifsEnabled) { style.push(styles.notificationOptionDescriptionTextDisabledSelected); } else if (!bannerNotifsEnabled) { style.push(styles.notificationOptionDescriptionTextDisabled); } return style; }, [ bannerNotifsEnabled, selected, styles.notificationOptionDescriptionText, styles.notificationOptionDescriptionTextDisabled, styles.notificationOptionDescriptionTextDisabledSelected, ]); - const badgeCountDescriptionTextStyles = React.useMemo(() => { + const notifCountDescriptionTextStyles = React.useMemo(() => { const style = [styles.notificationOptionDescriptionText]; - if (selected && !badgeCountEnabled) { + if (selected && !notifCountEnabled) { style.push(styles.notificationOptionDescriptionTextDisabledSelected); - } else if (!badgeCountEnabled) { + } else if (!notifCountEnabled) { style.push(styles.notificationOptionDescriptionTextDisabled); } return style; }, [ - badgeCountEnabled, + notifCountEnabled, selected, styles.notificationOptionDescriptionText, styles.notificationOptionDescriptionTextDisabled, styles.notificationOptionDescriptionTextDisabledSelected, ]); let bannerNotifsIconColor = colors.panelForegroundSecondaryLabel; if (selected && !bannerNotifsEnabled) { bannerNotifsIconColor = colors.panelInputSecondaryForeground; } else if (!bannerNotifsEnabled) { bannerNotifsIconColor = colors.panelSecondaryForeground; } - let badgeCountIconColor = colors.panelForegroundSecondaryLabel; - if (selected && !badgeCountEnabled) { - badgeCountIconColor = colors.panelInputSecondaryForeground; - } else if (!badgeCountEnabled) { - badgeCountIconColor = colors.panelSecondaryForeground; + let notifCountIconColor = colors.panelForegroundSecondaryLabel; + if (selected && !notifCountEnabled) { + notifCountIconColor = colors.panelInputSecondaryForeground; + } else if (!notifCountEnabled) { + notifCountIconColor = colors.panelSecondaryForeground; } return ( <> {threadSettingsNotificationsCopy.BANNER_NOTIFS} - - {threadSettingsNotificationsCopy.BADGE_COUNT} + + {threadSettingsNotificationsCopy.NOTIF_COUNT} {livesInFocusedTab ? threadSettingsNotificationsCopy.IN_FOCUSED_TAB : threadSettingsNotificationsCopy.IN_BACKGROUND_TAB} ); } type Props = { +navigation: ChatNavigationProp<'ThreadSettingsNotifications'>, +route: NavigationRoute<'ThreadSettingsNotifications'>, }; function ThreadSettingsNotifications(props: Props): React.Node { const { navigation: { setOptions, goBack }, route: { params: { threadInfo }, }, } = props; const { notificationSettings, onFocusedSelected, onBadgeOnlySelected, onBackgroundSelected, saveButtonDisabled, onSave, isSidebar, canPromoteSidebar, parentThreadIsInBackground, } = useThreadSettingsNotifications(threadInfo, goBack); React.useEffect(() => { setOptions({ headerRight: () => parentThreadIsInBackground ? null : ( ), }); }, [saveButtonDisabled, onSave, setOptions, parentThreadIsInBackground]); const styles = useStyles(unboundStyles); const allNotificationsIllustration = React.useMemo( () => ( ), [styles.notificationOptionIconContainer], ); const badgeOnlyIllustration = React.useMemo( () => ( ), [styles.notificationOptionIconContainer], ); const mutedIllustration = React.useMemo( () => ( ), [styles.notificationOptionIconContainer], ); const allNotificationsDescription = React.useMemo( () => ( ), [notificationSettings], ); const badgeOnlyDescription = React.useMemo( () => ( ), [notificationSettings], ); const mutedDescription = React.useMemo( () => ( ), [notificationSettings], ); const noticeText = React.useMemo(() => { if (!isSidebar) { return null; } return ( {threadSettingsNotificationsCopy.IS_SIDEBAR} {canPromoteSidebar ? threadSettingsNotificationsCopy.IS_SIDEBAR_CAN_PROMOTE : threadSettingsNotificationsCopy.IS_SIDEBAR_CAN_NOT_PROMOTE} ); }, [ canPromoteSidebar, isSidebar, styles.noticeText, styles.noticeTextContainer, ]); const threadSettingsNotifications = React.useMemo(() => { if (parentThreadIsInBackground) { return ( {threadSettingsNotificationsCopy.PARENT_THREAD_IS_BACKGROUND} {canPromoteSidebar ? threadSettingsNotificationsCopy.PARENT_THREAD_IS_BACKGROUND_CAN_PROMOTE : threadSettingsNotificationsCopy.PARENT_THREAD_IS_BACKGROUND_CAN_NOT_PROMOTE} ); } return ( {noticeText} ); }, [ allNotificationsDescription, allNotificationsIllustration, badgeOnlyDescription, badgeOnlyIllustration, canPromoteSidebar, isSidebar, mutedDescription, mutedIllustration, noticeText, notificationSettings, onBackgroundSelected, onBadgeOnlySelected, onFocusedSelected, parentThreadIsInBackground, styles.container, styles.enumSettingsOptionContainer, styles.parentThreadIsInBackgroundNoticeContainerStyle, styles.parentThreadIsInBackgroundNoticeText, ]); return threadSettingsNotifications; } const unboundStyles = { container: { backgroundColor: 'panelForeground', }, enumSettingsOptionContainer: { padding: 8, }, notificationOptionIconContainer: { justifyContent: 'center', marginLeft: 8, marginRight: 16, }, notificationOptionDescriptionListItem: { flexDirection: 'row', alignItems: 'center', marginTop: 4, }, notificationOptionDescriptionText: { color: 'panelForegroundSecondaryLabel', marginLeft: 4, fontSize: 14, }, notificationOptionDescriptionTextDisabled: { textDecorationLine: 'line-through', color: 'panelSecondaryForeground', }, notificationOptionDescriptionTextDisabledSelected: { color: 'panelInputSecondaryForeground', textDecorationLine: 'line-through', }, noticeTextContainer: { padding: 16, }, noticeText: { color: 'panelForegroundSecondaryLabel', textAlign: 'center', fontSize: 14, lineHeight: 18, marginVertical: 8, }, parentThreadIsInBackgroundNoticeContainerStyle: { backgroundColor: 'panelForeground', paddingHorizontal: 16, paddingVertical: 8, }, parentThreadIsInBackgroundNoticeText: { color: 'panelForegroundSecondaryLabel', fontSize: 16, lineHeight: 20, textAlign: 'left', marginVertical: 8, }, }; export default ThreadSettingsNotifications; diff --git a/web/modals/threads/notifications/notifications-modal.react.js b/web/modals/threads/notifications/notifications-modal.react.js index c4b9a5f74..60956a110 100644 --- a/web/modals/threads/notifications/notifications-modal.react.js +++ b/web/modals/threads/notifications/notifications-modal.react.js @@ -1,219 +1,219 @@ // @flow import * as React from 'react'; import { threadInfoSelector } from 'lib/selectors/thread-selectors.js'; import { threadSettingsNotificationsCopy, useThreadSettingsNotifications, } from 'lib/shared/thread-settings-notifications-utils.js'; import css from './notifications-modal.css'; import AllNotifsIllustration from '../../../assets/all-notifs.react.js'; import BadgeNotifsIllustration from '../../../assets/badge-notifs.react.js'; import MutedNotifsIllustration from '../../../assets/muted-notifs.react.js'; import Button from '../../../components/button.react.js'; import EnumSettingsOption from '../../../components/enum-settings-option.react.js'; import { useSelector } from '../../../redux/redux-utils.js'; import Modal from '../../modal.react.js'; const focusedStatements = [ { statement: threadSettingsNotificationsCopy.BANNER_NOTIFS, isStatementValid: true, styleStatementBasedOnValidity: true, }, { - statement: threadSettingsNotificationsCopy.BADGE_COUNT, + statement: threadSettingsNotificationsCopy.NOTIF_COUNT, isStatementValid: true, styleStatementBasedOnValidity: true, }, { statement: threadSettingsNotificationsCopy.IN_FOCUSED_TAB, isStatementValid: true, styleStatementBasedOnValidity: true, }, ]; const badgeOnlyStatements = [ { statement: threadSettingsNotificationsCopy.BANNER_NOTIFS, isStatementValid: false, styleStatementBasedOnValidity: true, }, { - statement: threadSettingsNotificationsCopy.BADGE_COUNT, + statement: threadSettingsNotificationsCopy.NOTIF_COUNT, isStatementValid: true, styleStatementBasedOnValidity: true, }, { statement: threadSettingsNotificationsCopy.IN_FOCUSED_TAB, isStatementValid: true, styleStatementBasedOnValidity: true, }, ]; const backgroundStatements = [ { statement: threadSettingsNotificationsCopy.BANNER_NOTIFS, isStatementValid: false, styleStatementBasedOnValidity: true, }, { - statement: threadSettingsNotificationsCopy.BADGE_COUNT, + statement: threadSettingsNotificationsCopy.NOTIF_COUNT, isStatementValid: false, styleStatementBasedOnValidity: true, }, { statement: threadSettingsNotificationsCopy.IN_BACKGROUND_TAB, isStatementValid: true, styleStatementBasedOnValidity: true, }, ]; type Props = { +threadID: string, +onClose: () => void, }; function NotificationsModal(props: Props): React.Node { const { onClose, threadID } = props; const threadInfo = useSelector(state => threadInfoSelector(state)[threadID]); const { notificationSettings, onFocusedSelected, onBadgeOnlySelected, onBackgroundSelected, saveButtonDisabled, onSave, isSidebar, canPromoteSidebar, parentThreadIsInBackground, } = useThreadSettingsNotifications(threadInfo, onClose); const isFocusedSelected = notificationSettings === 'focused'; const focusedItem = React.useMemo(() => { const icon = ; return ( ); }, [isFocusedSelected, onFocusedSelected]); const isFocusedBadgeOnlySelected = notificationSettings === 'badge-only'; const focusedBadgeOnlyItem = React.useMemo(() => { const icon = ; return ( ); }, [isFocusedBadgeOnlySelected, onBadgeOnlySelected]); const isBackgroundSelected = notificationSettings === 'background'; const backgroundItem = React.useMemo(() => { const icon = ; return ( ); }, [isBackgroundSelected, onBackgroundSelected, isSidebar]); const modalName = isSidebar ? threadSettingsNotificationsCopy.SIDEBAR_TITLE : threadSettingsNotificationsCopy.CHANNEL_TITLE; const noticeText = React.useMemo(() => { if (!isSidebar) { return null; } return ( <>

{threadSettingsNotificationsCopy.IS_SIDEBAR}

{canPromoteSidebar ? threadSettingsNotificationsCopy.IS_SIDEBAR_CAN_PROMOTE : threadSettingsNotificationsCopy.IS_SIDEBAR_CAN_NOT_PROMOTE}

); }, [isSidebar, canPromoteSidebar]); const modalContent = React.useMemo(() => { if (parentThreadIsInBackground) { return ( <>

{threadSettingsNotificationsCopy.PARENT_THREAD_IS_BACKGROUND}

{canPromoteSidebar ? threadSettingsNotificationsCopy.PARENT_THREAD_IS_BACKGROUND_CAN_PROMOTE : threadSettingsNotificationsCopy.PARENT_THREAD_IS_BACKGROUND_CAN_NOT_PROMOTE}

); } return ( <>
{focusedItem} {focusedBadgeOnlyItem} {backgroundItem}
{noticeText} ); }, [ backgroundItem, focusedBadgeOnlyItem, focusedItem, noticeText, parentThreadIsInBackground, canPromoteSidebar, ]); const saveButton = React.useMemo(() => { if (parentThreadIsInBackground) { return undefined; } return ( ); }, [saveButtonDisabled, onSave, parentThreadIsInBackground]); return (
{modalContent}
); } export default NotificationsModal;