diff --git a/app/hooks/android_back_handler.tsx b/app/hooks/android_back_handler.ts similarity index 100% rename from app/hooks/android_back_handler.tsx rename to app/hooks/android_back_handler.ts diff --git a/app/hooks/navigate_back.ts b/app/hooks/navigate_back.ts new file mode 100644 index 0000000000..8b595cfba7 --- /dev/null +++ b/app/hooks/navigate_back.ts @@ -0,0 +1,21 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {EffectCallback, useEffect} from 'react'; +import {Navigation} from 'react-native-navigation'; + +const BACK_BUTTON = 'RNN.back'; + +const useBackNavigation = (callback: EffectCallback) => { + useEffect(() => { + const backListener = Navigation.events().registerNavigationButtonPressedListener(({buttonId}) => { + if (buttonId === BACK_BUTTON) { + callback(); + } + }); + + return () => backListener.remove(); + }, [callback]); +}; + +export default useBackNavigation; diff --git a/app/hooks/navigation_button_pressed.tsx b/app/hooks/navigation_button_pressed.ts similarity index 100% rename from app/hooks/navigation_button_pressed.tsx rename to app/hooks/navigation_button_pressed.ts diff --git a/app/screens/settings/config.ts b/app/screens/settings/config.ts index 6b39e0ac2e..4617dcebbf 100644 --- a/app/screens/settings/config.ts +++ b/app/screens/settings/config.ts @@ -2,6 +2,7 @@ // See LICENSE.txt for license information. import {t} from '@i18n'; +import {goToScreen} from '@screens/navigation'; import {typography} from '@utils/typography'; import type {IntlShape} from 'react-intl'; @@ -16,6 +17,18 @@ export const getSaveButton = (buttonId: string, intl: IntlShape, color: string) ...typography('Body', 100, 'SemiBold'), }); +export const gotoSettingsScreen = (screen: string, title: string) => { + const passProps = {}; + const options = { + topBar: { + backButton: { + popStackOnPress: false, + }, + }, + }; + return goToScreen(screen, title, passProps, options); +}; + type SettingConfigDetails = { defaultMessage?: string; i18nId?: string; diff --git a/app/screens/settings/display/display.tsx b/app/screens/settings/display/display.tsx index 4dd0151cb0..b6b5d9a45a 100644 --- a/app/screens/settings/display/display.tsx +++ b/app/screens/settings/display/display.tsx @@ -8,6 +8,7 @@ import {Screens} from '@constants'; import {useTheme} from '@context/theme'; import {t} from '@i18n'; import {goToScreen} from '@screens/navigation'; +import {gotoSettingsScreen} from '@screens/settings/config'; import {preventDoubleTap} from '@utils/tap'; import {getUserTimezoneProps} from '@utils/user'; @@ -52,21 +53,19 @@ const Display = ({currentUser, hasMilitaryTimeFormat, isThemeSwitchingEnabled, i const goToThemeSettings = preventDoubleTap(() => { const screen = Screens.SETTINGS_DISPLAY_THEME; const title = intl.formatMessage({id: 'display_settings.theme', defaultMessage: 'Theme'}); - goToScreen(screen, title); }); const goToClockDisplaySettings = preventDoubleTap(() => { const screen = Screens.SETTINGS_DISPLAY_CLOCK; const title = intl.formatMessage({id: 'display_settings.clockDisplay', defaultMessage: 'Clock Display'}); - goToScreen(screen, title); + gotoSettingsScreen(screen, title); }); const goToTimezoneSettings = preventDoubleTap(() => { const screen = Screens.SETTINGS_DISPLAY_TIMEZONE; const title = intl.formatMessage({id: 'display_settings.timezone', defaultMessage: 'Timezone'}); - - goToScreen(screen, title); + gotoSettingsScreen(screen, title); }); return ( diff --git a/app/screens/settings/display_clock/display_clock.tsx b/app/screens/settings/display_clock/display_clock.tsx index 11f8e0266c..f03bdf306e 100644 --- a/app/screens/settings/display_clock/display_clock.tsx +++ b/app/screens/settings/display_clock/display_clock.tsx @@ -1,18 +1,16 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {useCallback, useEffect, useMemo, useState} from 'react'; +import React, {useCallback, useState} from 'react'; import {useIntl} from 'react-intl'; import {savePreference} from '@actions/remote/preference'; import {Preferences} from '@constants'; import {useServerUrl} from '@context/server'; -import {useTheme} from '@context/theme'; import useAndroidHardwareBackHandler from '@hooks/android_back_handler'; -import useNavButtonPressed from '@hooks/navigation_button_pressed'; -import {popTopScreen, setButtons} from '@screens/navigation'; +import useBackNavigation from '@hooks/navigate_back'; +import {popTopScreen} from '@screens/navigation'; -import {getSaveButton} from '../config'; import SettingBlock from '../setting_block'; import SettingContainer from '../setting_container'; import SettingOption from '../setting_option'; @@ -23,51 +21,40 @@ const CLOCK_TYPE = { MILITARY: 'MILITARY', } as const; -const SAVE_CLOCK_BUTTON_ID = 'settings_display.clock.save.button'; - type DisplayClockProps = { componentId: string; currentUserId: string; hasMilitaryTimeFormat: boolean; } const DisplayClock = ({componentId, currentUserId, hasMilitaryTimeFormat}: DisplayClockProps) => { - const theme = useTheme(); const [isMilitaryTimeFormat, setIsMilitaryTimeFormat] = useState(hasMilitaryTimeFormat); const serverUrl = useServerUrl(); const intl = useIntl(); - const saveButton = useMemo(() => getSaveButton(SAVE_CLOCK_BUTTON_ID, intl, theme.sidebarHeaderTextColor), [theme.sidebarHeaderTextColor]); - const onSelectClockPreference = useCallback((clockType: keyof typeof CLOCK_TYPE) => { setIsMilitaryTimeFormat(clockType === CLOCK_TYPE.MILITARY); }, []); const close = () => popTopScreen(componentId); - const saveClockDisplayPreference = () => { - const timePreference: PreferenceType = { - category: Preferences.CATEGORY_DISPLAY_SETTINGS, - name: 'use_military_time', - user_id: currentUserId, - value: `${isMilitaryTimeFormat}`, - }; + const saveClockDisplayPreference = useCallback(() => { + if (hasMilitaryTimeFormat !== isMilitaryTimeFormat) { + const timePreference: PreferenceType = { + category: Preferences.CATEGORY_DISPLAY_SETTINGS, + name: 'use_military_time', + user_id: currentUserId, + value: `${isMilitaryTimeFormat}`, + }; + + savePreference(serverUrl, [timePreference]); + } - savePreference(serverUrl, [timePreference]); close(); - }; + }, [hasMilitaryTimeFormat, isMilitaryTimeFormat, serverUrl]); - useEffect(() => { - const buttons = { - rightButtons: [{ - ...saveButton, - enabled: hasMilitaryTimeFormat !== isMilitaryTimeFormat, - }], - }; - setButtons(componentId, buttons); - }, [componentId, saveButton, isMilitaryTimeFormat]); + useBackNavigation(saveClockDisplayPreference); - useAndroidHardwareBackHandler(componentId, close); - useNavButtonPressed(SAVE_CLOCK_BUTTON_ID, componentId, saveClockDisplayPreference, [isMilitaryTimeFormat]); + useAndroidHardwareBackHandler(componentId, saveClockDisplayPreference); return ( diff --git a/app/screens/settings/display_theme/display_theme.tsx b/app/screens/settings/display_theme/display_theme.tsx index 906c533c0f..dda3867868 100644 --- a/app/screens/settings/display_theme/display_theme.tsx +++ b/app/screens/settings/display_theme/display_theme.tsx @@ -1,25 +1,20 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {useCallback, useEffect, useMemo, useState} from 'react'; -import {useIntl} from 'react-intl'; +import React, {useCallback, useMemo} from 'react'; import {savePreference} from '@actions/remote/preference'; import {Preferences} from '@constants'; import {useServerUrl} from '@context/server'; import {useTheme} from '@context/theme'; import useAndroidHardwareBackHandler from '@hooks/android_back_handler'; -import useNavButtonPressed from '@hooks/navigation_button_pressed'; -import {popTopScreen, setButtons} from '@screens/navigation'; -import {getSaveButton} from '@screens/settings/config'; +import {popTopScreen} from '@screens/navigation'; import SettingContainer from '../setting_container'; import CustomTheme from './custom_theme'; import {ThemeTiles} from './theme_tiles'; -const SAVE_DISPLAY_THEME_BTN_ID = 'SAVE_DISPLAY_THEME_BTN_ID'; - type DisplayThemeProps = { allowedThemeKeys: string[]; componentId: string; @@ -29,55 +24,41 @@ type DisplayThemeProps = { const DisplayTheme = ({allowedThemeKeys, componentId, currentTeamId, currentUserId}: DisplayThemeProps) => { const serverUrl = useServerUrl(); const theme = useTheme(); - const intl = useIntl(); - const initialTheme = useMemo(() => theme.type, []); // dependency array should remain empty - - const [displayTheme, setDisplayTheme] = useState(initialTheme); - - const saveButton = useMemo(() => getSaveButton(SAVE_DISPLAY_THEME_BTN_ID, intl, theme.sidebarHeaderTextColor), [theme.sidebarHeaderTextColor]); + const initialTheme = useMemo(() => theme.type, [/* dependency array should remain empty */]); const close = () => popTopScreen(componentId); - const updateTheme = useCallback(() => { - const selectedTheme = allowedThemeKeys.find((tk) => tk === displayTheme); - if (!selectedTheme) { + const setThemePreference = useCallback((newTheme?: string) => { + const allowedTheme = allowedThemeKeys.find((tk) => tk === newTheme); + const differentTheme = initialTheme?.toLowerCase() !== newTheme?.toLowerCase(); + + if (!allowedTheme || !differentTheme) { + close(); return; } + const pref: PreferenceType = { category: Preferences.CATEGORY_THEME, name: currentTeamId, user_id: currentUserId, - value: JSON.stringify(Preferences.THEMES[selectedTheme]), + value: JSON.stringify(Preferences.THEMES[allowedTheme]), }; savePreference(serverUrl, [pref]); - close(); - }, [serverUrl, allowedThemeKeys, currentTeamId, displayTheme]); + }, [allowedThemeKeys, currentTeamId, initialTheme, serverUrl]); - useEffect(() => { - const buttons = { - rightButtons: [{ - ...saveButton, - enabled: initialTheme?.toLowerCase() !== displayTheme?.toLowerCase(), - }], - }; - setButtons(componentId, buttons); - }, [componentId, saveButton, displayTheme, initialTheme]); - - useNavButtonPressed(SAVE_DISPLAY_THEME_BTN_ID, componentId, updateTheme, [updateTheme]); - - useAndroidHardwareBackHandler(componentId, close); + useAndroidHardwareBackHandler(componentId, setThemePreference); return ( {theme.type === 'custom' && ( )} diff --git a/app/screens/settings/display_timezone/display_timezone.tsx b/app/screens/settings/display_timezone/display_timezone.tsx index 220253c108..4cfb776d4a 100644 --- a/app/screens/settings/display_timezone/display_timezone.tsx +++ b/app/screens/settings/display_timezone/display_timezone.tsx @@ -1,29 +1,25 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {useCallback, useEffect, useMemo, useState} from 'react'; +import React, {useCallback, useMemo, useState} from 'react'; import {useIntl} from 'react-intl'; import {updateMe} from '@actions/remote/user'; import {Screens} from '@constants'; import {useServerUrl} from '@context/server'; -import {useTheme} from '@context/theme'; import useAndroidHardwareBackHandler from '@hooks/android_back_handler'; -import useNavButtonPressed from '@hooks/navigation_button_pressed'; -import {goToScreen, popTopScreen, setButtons} from '@screens/navigation'; +import useBackNavigation from '@hooks/navigate_back'; +import {goToScreen, popTopScreen} from '@screens/navigation'; import {preventDoubleTap} from '@utils/tap'; import {getDeviceTimezone} from '@utils/timezone'; import {getTimezoneRegion, getUserTimezoneProps} from '@utils/user'; -import {getSaveButton} from '../config'; import SettingContainer from '../setting_container'; import SettingOption from '../setting_option'; import SettingSeparator from '../settings_separator'; import type UserModel from '@typings/database/models/servers/user'; -const SAVE_TIMEZONE_BUTTON_ID = 'save_timezone'; - type DisplayTimezoneProps = { currentUser: UserModel; componentId: string; @@ -31,9 +27,8 @@ type DisplayTimezoneProps = { const DisplayTimezone = ({currentUser, componentId}: DisplayTimezoneProps) => { const intl = useIntl(); const serverUrl = useServerUrl(); - const initialTimezone = useMemo(() => getUserTimezoneProps(currentUser), []); // deps array should remain empty + const initialTimezone = useMemo(() => getUserTimezoneProps(currentUser), [/* dependency array should remain empty */]); const [userTimezone, setUserTimezone] = useState(initialTimezone); - const theme = useTheme(); const updateAutomaticTimezone = (useAutomaticTimezone: boolean) => { const automaticTimezone = getDeviceTimezone(); @@ -67,36 +62,27 @@ const DisplayTimezone = ({currentUser, componentId}: DisplayTimezoneProps) => { const close = () => popTopScreen(componentId); const saveTimezone = useCallback(() => { - const timeZone = { - useAutomaticTimezone: userTimezone.useAutomaticTimezone.toString(), - automaticTimezone: userTimezone.automaticTimezone, - manualTimezone: userTimezone.manualTimezone, - }; - - updateMe(serverUrl, {timezone: timeZone}); - close(); - }, [userTimezone, currentUser.timezone, serverUrl]); - - const saveButton = useMemo(() => getSaveButton(SAVE_TIMEZONE_BUTTON_ID, intl, theme.sidebarHeaderTextColor), [theme.sidebarHeaderTextColor]); - - useEffect(() => { - const enabled = + const canSave = initialTimezone.useAutomaticTimezone !== userTimezone.useAutomaticTimezone || initialTimezone.automaticTimezone !== userTimezone.automaticTimezone || initialTimezone.manualTimezone !== userTimezone.manualTimezone; - const buttons = { - rightButtons: [{ - ...saveButton, - enabled, - }], - }; - setButtons(componentId, buttons); - }, [componentId, currentUser.timezone]); + if (canSave) { + const timeZone = { + useAutomaticTimezone: userTimezone.useAutomaticTimezone.toString(), + automaticTimezone: userTimezone.automaticTimezone, + manualTimezone: userTimezone.manualTimezone, + }; - useNavButtonPressed(SAVE_TIMEZONE_BUTTON_ID, componentId, saveTimezone, [saveTimezone]); + updateMe(serverUrl, {timezone: timeZone}); + } - useAndroidHardwareBackHandler(componentId, close); + close(); + }, [userTimezone, currentUser.timezone, serverUrl]); + + useBackNavigation(saveTimezone); + + useAndroidHardwareBackHandler(componentId, saveTimezone); const toggleDesc = useMemo(() => { if (userTimezone.useAutomaticTimezone) { @@ -116,17 +102,13 @@ const DisplayTimezone = ({currentUser, componentId}: DisplayTimezoneProps) => { /> {!userTimezone.useAutomaticTimezone && ( - <> - {/* */} - - + )} - {/* */} ); }; diff --git a/app/screens/settings/display_timezone_select/index.tsx b/app/screens/settings/display_timezone_select/index.tsx index 993733b3f2..30ec34401e 100644 --- a/app/screens/settings/display_timezone_select/index.tsx +++ b/app/screens/settings/display_timezone_select/index.tsx @@ -11,9 +11,7 @@ import Search from '@components/search'; import {useServerUrl} from '@context/server'; import {useTheme} from '@context/theme'; import useAndroidHardwareBackHandler from '@hooks/android_back_handler'; -import useNavButtonPressed from '@hooks/navigation_button_pressed'; -import {popTopScreen, setButtons} from '@screens/navigation'; -import {getSaveButton} from '@screens/settings/config'; +import {popTopScreen} from '@screens/navigation'; import {changeOpacity, getKeyboardAppearanceFromTheme, makeStyleSheetFromTheme} from '@utils/theme'; import {typography} from '@utils/typography'; import {getTimezoneRegion} from '@utils/user'; @@ -48,7 +46,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { const EDGES: Edge[] = ['left', 'right']; const EMPTY_TIMEZONES: string[] = []; const ITEM_HEIGHT = 48; -const SAVE_DISPLAY_TZ_BTN_ID = 'SAVE_DISPLAY_TZ_BTN_ID'; const keyExtractor = (item: string) => item; const getItemLayout = (_data: string[], index: number) => ({ length: ITEM_HEIGHT, @@ -66,7 +63,7 @@ const SelectTimezones = ({componentId, onBack, currentTimezone}: SelectTimezones const serverUrl = useServerUrl(); const theme = useTheme(); const styles = getStyleSheet(theme); - const initialTimezones = useMemo(() => currentTimezone, []); + const cancelButtonProps = useMemo(() => ({ buttonTextStyle: { color: changeOpacity(theme.centerChannelColor, 0.64), @@ -80,7 +77,6 @@ const SelectTimezones = ({componentId, onBack, currentTimezone}: SelectTimezones const [timezones, setTimezones] = useState(EMPTY_TIMEZONES); const [initialScrollIndex, setInitialScrollIndex] = useState(); const [searchRegion, setSearchRegion] = useState(undefined); - const [manualTimezone, setManualTimezone] = useState(currentTimezone); const filteredTimezones = useCallback(() => { if (!searchRegion) { @@ -103,26 +99,24 @@ const SelectTimezones = ({componentId, onBack, currentTimezone}: SelectTimezones )); }, [searchRegion, timezones, initialScrollIndex]); - const onPressTimezone = useCallback((tz: string) => { - setManualTimezone(tz); + const close = (newTimezone?: string) => { + onBack(newTimezone || currentTimezone); + popTopScreen(componentId); + }; + + const onPressTimezone = useCallback((selectedTimezone: string) => { + close(selectedTimezone); }, []); const renderItem = useCallback(({item: timezone}: {item: string}) => { return ( ); - }, [manualTimezone, onPressTimezone]); - - const saveButton = useMemo(() => getSaveButton(SAVE_DISPLAY_TZ_BTN_ID, intl, theme.sidebarHeaderTextColor), [theme.sidebarHeaderTextColor]); - - const close = () => { - onBack(manualTimezone); - popTopScreen(componentId); - }; + }, [currentTimezone, onPressTimezone]); useEffect(() => { // let's get all supported timezones @@ -139,18 +133,6 @@ const SelectTimezones = ({componentId, onBack, currentTimezone}: SelectTimezones getSupportedTimezones(); }, []); - useEffect(() => { - const buttons = { - rightButtons: [{ - ...saveButton, - enabled: initialTimezones !== manualTimezone, - }], - }; - setButtons(componentId, buttons); - }, [componentId, saveButton, initialTimezones, manualTimezone]); - - useNavButtonPressed(SAVE_DISPLAY_TZ_BTN_ID, componentId, close, [manualTimezone]); - useAndroidHardwareBackHandler(componentId, close); return ( @@ -176,7 +158,6 @@ const SelectTimezones = ({componentId, onBack, currentTimezone}: SelectTimezones { return { @@ -66,46 +64,37 @@ const NotificationAutoResponder = ({currentUser, componentId}: NotificationAutoR const theme = useTheme(); const serverUrl = useServerUrl(); const intl = useIntl(); - const notifyProps = useMemo(() => getNotificationProps(currentUser), []); // dependency array should remain empty + const notifyProps = useMemo(() => getNotificationProps(currentUser), [/* dependency array should remain empty */]); - const initialAutoResponderActive = useMemo(() => Boolean(currentUser.status === General.OUT_OF_OFFICE && notifyProps.auto_responder_active === 'true'), []); // dependency array should remain empty + const initialAutoResponderActive = useMemo(() => Boolean(currentUser.status === General.OUT_OF_OFFICE && notifyProps.auto_responder_active === 'true'), [/* dependency array should remain empty */]); const [autoResponderActive, setAutoResponderActive] = useState(initialAutoResponderActive); - const initialOOOMsg = useMemo(() => notifyProps.auto_responder_message || intl.formatMessage(OOO), []); // dependency array should remain empty + const initialOOOMsg = useMemo(() => notifyProps.auto_responder_message || intl.formatMessage(OOO), [/* dependency array should remain empty */]); const [autoResponderMessage, setAutoResponderMessage] = useState(initialOOOMsg); const styles = getStyleSheet(theme); const close = () => popTopScreen(componentId); - const saveButton = useMemo(() => getSaveButton(SAVE_OOO_BUTTON_ID, intl, theme.sidebarHeaderTextColor), [theme.sidebarHeaderTextColor]); - const saveAutoResponder = useCallback(() => { - updateMe(serverUrl, { - notify_props: { - ...notifyProps, - auto_responder_active: `${autoResponderActive}`, - auto_responder_message: autoResponderMessage, - }, - }); - fetchStatusInBatch(serverUrl, currentUser.id); + const canSaveSetting = initialAutoResponderActive !== autoResponderActive || initialOOOMsg !== autoResponderMessage; + + if (canSaveSetting) { + updateMe(serverUrl, { + notify_props: { + ...notifyProps, + auto_responder_active: `${autoResponderActive}`, + auto_responder_message: autoResponderMessage, + }, + }); + fetchStatusInBatch(serverUrl, currentUser.id); + } close(); }, [serverUrl, autoResponderActive, autoResponderMessage, notifyProps, currentUser.id]); - useEffect(() => { - const enabled = initialAutoResponderActive !== autoResponderActive || initialOOOMsg !== autoResponderMessage; - const buttons = { - rightButtons: [{ - ...saveButton, - enabled, - }], - }; - setButtons(componentId, buttons); - }, [autoResponderActive, autoResponderMessage, componentId, currentUser.status, notifyProps.auto_responder_message]); + useBackNavigation(saveAutoResponder); - useNavButtonPressed(SAVE_OOO_BUTTON_ID, componentId, saveAutoResponder, [saveAutoResponder]); - - useAndroidHardwareBackHandler(componentId, close); + useAndroidHardwareBackHandler(componentId, saveAutoResponder); return ( diff --git a/app/screens/settings/notification_email/notification_email.tsx b/app/screens/settings/notification_email/notification_email.tsx index 8b9fd583ec..aec81b66d4 100644 --- a/app/screens/settings/notification_email/notification_email.tsx +++ b/app/screens/settings/notification_email/notification_email.tsx @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {useCallback, useEffect, useMemo, useState} from 'react'; +import React, {useCallback, useMemo, useState} from 'react'; import {useIntl} from 'react-intl'; import {Text} from 'react-native'; @@ -11,14 +11,13 @@ import {Preferences} from '@constants'; import {useServerUrl} from '@context/server'; import {useTheme} from '@context/theme'; import useAndroidHardwareBackHandler from '@hooks/android_back_handler'; -import useNavButtonPressed from '@hooks/navigation_button_pressed'; +import useBackNavigation from '@hooks/navigate_back'; import {t} from '@i18n'; -import {popTopScreen, setButtons} from '@screens/navigation'; +import {popTopScreen} from '@screens/navigation'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import {typography} from '@utils/typography'; import {getEmailInterval, getNotificationProps} from '@utils/user'; -import {getSaveButton} from '../config'; import SettingBlock from '../setting_block'; import SettingContainer from '../setting_container'; import SettingOption from '../setting_option'; @@ -31,6 +30,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { disabled: { color: changeOpacity(theme.centerChannelColor, 0.64), ...typography('Body', 75, 'Regular'), + marginHorizontal: 20, }, }; }); @@ -53,8 +53,6 @@ const emailFooterCRTText = { defaultMessage: "When enabled, any reply to a thread you're following will send an email notification", }; -const SAVE_EMAIL_BUTTON_ID = 'settings_notification.email.save.button'; - type NotificationEmailProps = { componentId: string; currentUser: UserModel; @@ -66,12 +64,11 @@ type NotificationEmailProps = { const NotificationEmail = ({componentId, currentUser, emailInterval, enableEmailBatching, isCRTEnabled, sendEmailNotifications}: NotificationEmailProps) => { const notifyProps = useMemo(() => getNotificationProps(currentUser), [currentUser.notifyProps]); - const initialInterval = useMemo(() => getEmailInterval( - sendEmailNotifications && notifyProps?.email === 'true', - enableEmailBatching, - parseInt(emailInterval, 10), - ).toString(), []); // dependency array should remain empty - const initialEmailThreads = useMemo(() => Boolean(notifyProps?.email_threads === 'all'), []); // dependency array should remain empty + const initialInterval = useMemo( + () => getEmailInterval(sendEmailNotifications && notifyProps?.email === 'true', enableEmailBatching, parseInt(emailInterval, 10)).toString(), + [/* dependency array should remain empty */], + ); + const initialEmailThreads = useMemo(() => Boolean(notifyProps?.email_threads === 'all'), [/* dependency array should remain empty */]); const [notifyInterval, setNotifyInterval] = useState(initialInterval); const [emailThreads, setEmailThreads] = useState(initialEmailThreads); @@ -81,47 +78,39 @@ const NotificationEmail = ({componentId, currentUser, emailInterval, enableEmail const theme = useTheme(); const styles = getStyleSheet(theme); - const saveButton = useMemo(() => getSaveButton(SAVE_EMAIL_BUTTON_ID, intl, theme.sidebarHeaderTextColor), [theme.sidebarHeaderTextColor]); - const close = () => popTopScreen(componentId); - const saveEmail = useCallback(async () => { - const promises = []; - const updatePromise = updateMe(serverUrl, { - notify_props: { - ...notifyProps, - email: `${sendEmailNotifications && notifyInterval !== Preferences.INTERVAL_NEVER.toString()}`, - email_threads: emailThreads ? 'all' : 'mention', - }, - }); - promises.push(updatePromise); + const saveEmail = useCallback(() => { + const canSaveSetting = notifyInterval !== initialInterval || emailThreads !== initialEmailThreads; + if (canSaveSetting) { + const promises = []; + const updatePromise = updateMe(serverUrl, { + notify_props: { + ...notifyProps, + email: `${sendEmailNotifications && notifyInterval !== Preferences.INTERVAL_NEVER.toString()}`, + ...(isCRTEnabled && {email_threads: emailThreads ? 'all' : 'mention'}), + }, + }); + promises.push(updatePromise); - if (notifyInterval !== initialInterval) { - const emailIntervalPreference = { - category: Preferences.CATEGORY_NOTIFICATIONS, - name: Preferences.EMAIL_INTERVAL, - user_id: currentUser.id, - value: notifyInterval, - }; - const savePrefPromise = savePreference(serverUrl, [emailIntervalPreference]); - promises.push(savePrefPromise); + if (notifyInterval !== initialInterval) { + const emailIntervalPreference = { + category: Preferences.CATEGORY_NOTIFICATIONS, + name: Preferences.EMAIL_INTERVAL, + user_id: currentUser.id, + value: notifyInterval, + }; + const savePrefPromise = savePreference(serverUrl, [emailIntervalPreference]); + promises.push(savePrefPromise); + } + Promise.all(promises); } - await Promise.all(promises); close(); }, [notifyProps, notifyInterval, emailThreads, serverUrl, currentUser.id, sendEmailNotifications]); - useEffect(() => { - const buttons = { - rightButtons: [{ - ...saveButton, - enabled: notifyInterval !== initialInterval || emailThreads !== initialEmailThreads, - }], - }; - setButtons(componentId, buttons); - }, [componentId, saveButton, notifyInterval, emailThreads]); + useAndroidHardwareBackHandler(componentId, saveEmail); - useAndroidHardwareBackHandler(componentId, close); - useNavButtonPressed(SAVE_EMAIL_BUTTON_ID, componentId, saveEmail, [saveEmail]); + useBackNavigation(saveEmail); return ( @@ -182,7 +171,7 @@ const NotificationEmail = ({componentId, currentUser, emailInterval, enableEmail } - {isCRTEnabled && notifyProps.email === 'true' && ( + {isCRTEnabled && notifyInterval !== Preferences.INTERVAL_NEVER.toString() && ( { return { input: { @@ -63,8 +61,11 @@ const getMentionProps = (currentUser: UserModel) => { } return { - mentionKeys: mKeys.join(','), + mentionKeywords: mKeys.join(','), usernameMention: usernameMentionIndex > -1, + channel: notifyProps.channel === 'true', + first_name: notifyProps.first_name === 'true', + comments: notifyProps.comments, notifyProps, }; }; @@ -72,134 +73,156 @@ const getMentionProps = (currentUser: UserModel) => { type MentionSectionProps = { componentId: string; currentUser: UserModel; + isCRTEnabled: boolean; } -const MentionSettings = ({componentId, currentUser}: MentionSectionProps) => { +const MentionSettings = ({componentId, currentUser, isCRTEnabled}: MentionSectionProps) => { const serverUrl = useServerUrl(); - const mentionProps = useMemo(() => getMentionProps(currentUser), [currentUser.notifyProps]); + const mentionProps = useMemo(() => getMentionProps(currentUser), []); + const notifyProps = mentionProps.notifyProps; - const notifyProps = currentUser.notifyProps || mentionProps.notifyProps; - const [tglFirstName, setTglFirstName] = useState(notifyProps.first_name === 'true'); - const [tglChannel, setTglChannel] = useState(notifyProps.channel === 'true'); - - const [tglUserName, setTglUserName] = useState(mentionProps.usernameMention); - const [mentionKeys, setMentionKeys] = useState(mentionProps.mentionKeys); + const [mentionKeywords, setMentionKeywords] = useState(mentionProps.mentionKeywords); + const [channelMentionOn, setChannelMentionOn] = useState(mentionProps.channel); + const [firstNameMentionOn, setFirstNameMentionOn] = useState(mentionProps.first_name); + const [usernameMentionOn, setUsernameMentionOn] = useState(mentionProps.usernameMention); + const [replyNotificationType, setReplyNotificationType] = useState(mentionProps.comments); const theme = useTheme(); const styles = getStyleSheet(theme); const intl = useIntl(); - const saveButton = useMemo(() => getSaveButton(SAVE_MENTION_BUTTON_ID, intl, theme.sidebarHeaderTextColor), [theme.sidebarHeaderTextColor]); - const close = () => popTopScreen(componentId); + const canSaveSettings = useCallback(() => { + const channelChanged = channelMentionOn !== mentionProps.channel; + const replyChanged = replyNotificationType !== mentionProps.comments; + const fNameChanged = firstNameMentionOn !== mentionProps.first_name; + const mnKeysChanged = mentionProps.mentionKeywords !== mentionKeywords; + const userNameChanged = usernameMentionOn !== mentionProps.usernameMention; + + return fNameChanged || userNameChanged || channelChanged || mnKeysChanged || replyChanged; + }, [firstNameMentionOn, channelMentionOn, usernameMentionOn, mentionKeywords, notifyProps, replyNotificationType]); + const saveMention = useCallback(() => { - const notify_props: UserNotifyProps = { - ...notifyProps, - first_name: `${tglFirstName}`, - channel: `${tglChannel}`, - mention_keys: mentionKeys}; - updateMe(serverUrl, {notify_props}); + const canSave = canSaveSettings(); + + if (canSave) { + const mention_keys = []; + if (mentionKeywords.length > 0) { + mentionKeywords.split(',').forEach((m) => mention_keys.push(m.replace(/\s/g, ''))); + } + + if (usernameMentionOn) { + mention_keys.push(`${currentUser.username}`); + } + const notify_props: UserNotifyProps = { + ...notifyProps, + first_name: `${firstNameMentionOn}`, + channel: `${channelMentionOn}`, + mention_keys: mention_keys.join(','), + comments: replyNotificationType, + }; + updateMe(serverUrl, {notify_props}); + } + close(); - }, [serverUrl, notifyProps, tglFirstName, tglChannel, mentionKeys]); + }, [ + canSaveSettings, + channelMentionOn, + firstNameMentionOn, + mentionKeywords, + notifyProps, + replyNotificationType, + serverUrl, + ]); const onToggleFirstName = useCallback(() => { - setTglFirstName((prev) => !prev); + setFirstNameMentionOn((prev) => !prev); }, []); const onToggleUserName = useCallback(() => { - setTglUserName((prev) => !prev); + setUsernameMentionOn((prev) => !prev); }, []); const onToggleChannel = useCallback(() => { - setTglChannel((prev) => !prev); + setChannelMentionOn((prev) => !prev); }, []); const onChangeText = useCallback((text: string) => { - setMentionKeys(text); + setMentionKeywords(text); }, []); - useEffect(() => { - const fNameChanged = tglFirstName !== Boolean(notifyProps.first_name); - const channelChanged = tglChannel !== Boolean(notifyProps.channel); + useBackNavigation(saveMention); - const usnChanged = tglUserName !== mentionProps.usernameMention; - const kwsChanged = mentionProps.mentionKeys !== mentionKeys; - - const enabled = fNameChanged || usnChanged || channelChanged || kwsChanged; - - const buttons = { - rightButtons: [{ - ...saveButton, - enabled, - }], - }; - setButtons(componentId, buttons); - }, [componentId, saveButton, tglFirstName, tglChannel, tglUserName, mentionKeys, notifyProps]); - - useNavButtonPressed(SAVE_MENTION_BUTTON_ID, componentId, saveMention, [saveMention]); - - useAndroidHardwareBackHandler(componentId, close); + useAndroidHardwareBackHandler(componentId, saveMention); return ( - - {Boolean(currentUser?.firstName) && ( - <> + <> + + {Boolean(currentUser?.firstName) && ( + <> + + + + ) + } + {Boolean(currentUser?.username) && ( - - - ) - } - {Boolean(currentUser?.username) && ( + )} + + + + + {intl.formatMessage({id: 'notification_settings.mentions.keywordsLabel', defaultMessage: 'Keywords are not case-sensitive. Separate keywords with commas.'})} + + + {!isCRTEnabled && ( + )} - - - - - - {intl.formatMessage({id: 'notification_settings.mentions.keywordsLabel', defaultMessage: 'Keywords are not case-sensitive. Separate keywords with commas.'})} - - + ); }; diff --git a/app/screens/settings/notification_mention/notification_mention.tsx b/app/screens/settings/notification_mention/notification_mention.tsx index 32094eb892..d50cb24c88 100644 --- a/app/screens/settings/notification_mention/notification_mention.tsx +++ b/app/screens/settings/notification_mention/notification_mention.tsx @@ -6,7 +6,6 @@ import React from 'react'; import SettingContainer from '../setting_container'; import MentionSettings from './mention_settings'; -import ReplySettings from './reply_settings'; import type UserModel from '@typings/database/models/servers/user'; @@ -21,8 +20,8 @@ const NotificationMention = ({componentId, currentUser, isCRTEnabled}: Notificat - {!isCRTEnabled && } ); }; diff --git a/app/screens/settings/notification_mention/reply_settings.tsx b/app/screens/settings/notification_mention/reply_settings.tsx index fd826bc7a4..1e295c611a 100644 --- a/app/screens/settings/notification_mention/reply_settings.tsx +++ b/app/screens/settings/notification_mention/reply_settings.tsx @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {useState} from 'react'; +import React, {Dispatch, SetStateAction} from 'react'; import {useIntl} from 'react-intl'; import {t} from '@i18n'; @@ -14,9 +14,11 @@ const replyHeaderText = { id: t('notification_settings.mention.reply'), defaultMessage: 'Send reply notifications for', }; - -const ReplySettings = () => { - const [replyNotificationType, setReplyNotificationType] = useState('any'); +type ReplySettingsProps = { + replyNotificationType: string; + setReplyNotificationType: Dispatch>; +} +const ReplySettings = ({replyNotificationType, setReplyNotificationType}: ReplySettingsProps) => { const intl = useIntl(); return ( diff --git a/app/screens/settings/notification_push/notification_push.tsx b/app/screens/settings/notification_push/notification_push.tsx index 679ca7e590..7398c8634c 100644 --- a/app/screens/settings/notification_push/notification_push.tsx +++ b/app/screens/settings/notification_push/notification_push.tsx @@ -1,20 +1,17 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {useCallback, useEffect, useMemo, useState} from 'react'; -import {useIntl} from 'react-intl'; +import React, {useCallback, useMemo, useState} from 'react'; import {Platform} from 'react-native'; import {updateMe} from '@actions/remote/user'; import {useServerUrl} from '@context/server'; -import {useTheme} from '@context/theme'; import useAndroidHardwareBackHandler from '@hooks/android_back_handler'; -import useNavButtonPressed from '@hooks/navigation_button_pressed'; -import {popTopScreen, setButtons} from '@screens/navigation'; +import useBackNavigation from '@hooks/navigate_back'; +import {popTopScreen} from '@screens/navigation'; import SettingSeparator from '@screens/settings/settings_separator'; import {getNotificationProps} from '@utils/user'; -import {getSaveButton} from '../config'; import SettingContainer from '../setting_container'; import MobileSendPush from './push_send'; @@ -23,8 +20,6 @@ import MobilePushThread from './push_thread'; import type UserModel from '@typings/database/models/servers/user'; -const SAVE_NOTIF_BUTTON_ID = 'SAVE_NOTIF_BUTTON_ID'; - type NotificationMobileProps = { componentId: string; currentUser: UserModel; @@ -36,52 +31,40 @@ const NotificationPush = ({componentId, currentUser, isCRTEnabled, sendPushNotif const notifyProps = useMemo(() => getNotificationProps(currentUser), [currentUser.notifyProps]); - const [pushSend, setPushSend] = useState(notifyProps.push); - const [pushStatus, setPushStatus] = useState(notifyProps.push_status); - const [pushThread, setPushThreadPref] = useState(notifyProps?.push_threads || 'all'); - - const intl = useIntl(); - const theme = useTheme(); + const [pushSend, setPushSend] = useState(notifyProps.push); + const [pushStatus, setPushStatus] = useState(notifyProps.push_status); + const [pushThread, setPushThreadPref] = useState(notifyProps?.push_threads || 'all'); const onMobilePushThreadChanged = useCallback(() => { setPushThreadPref(pushThread === 'all' ? 'mention' : 'all'); }, [pushThread]); - const saveButton = useMemo(() => getSaveButton(SAVE_NOTIF_BUTTON_ID, intl, theme.sidebarHeaderTextColor), [theme.sidebarHeaderTextColor]); + const close = () => popTopScreen(componentId); - const close = useCallback(() => popTopScreen(componentId), [componentId]); - - const saveNotificationSettings = useCallback(() => { - const notify_props = {...notifyProps, push: pushSend, push_status: pushStatus, push_threads: pushThread}; - updateMe(serverUrl, {notify_props} as unknown as UserNotifyProps); - close(); - }, [serverUrl, notifyProps, pushSend, pushStatus, pushThread, close]); - - useEffect(() => { + const canSaveSettings = useCallback(() => { const p = pushSend !== notifyProps.push; const pT = pushThread !== notifyProps.push_threads; const pS = pushStatus !== notifyProps.push_status; + return p || pT || pS; + }, [notifyProps, pushSend, pushStatus, pushThread]); - const enabled = p || pT || pS; + const saveNotificationSettings = useCallback(() => { + const canSave = canSaveSettings(); + if (canSave) { + const notify_props: UserNotifyProps = { + ...notifyProps, + push: pushSend, + push_status: pushStatus, + push_threads: pushThread, + }; + updateMe(serverUrl, {notify_props}); + } + close(); + }, [canSaveSettings, close, notifyProps, pushSend, pushStatus, pushThread, serverUrl]); - const buttons = { - rightButtons: [{ - ...saveButton, - enabled, - }], - }; - setButtons(componentId, buttons); - }, [ - componentId, - notifyProps, - pushSend, - pushStatus, - pushThread, - ]); + useBackNavigation(saveNotificationSettings); - useNavButtonPressed(SAVE_NOTIF_BUTTON_ID, componentId, saveNotificationSettings, [saveNotificationSettings]); - - useAndroidHardwareBackHandler(componentId, close); + useAndroidHardwareBackHandler(componentId, saveNotificationSettings); return ( diff --git a/app/screens/settings/notification_push/push_send.tsx b/app/screens/settings/notification_push/push_send.tsx index 5eedc91f9a..ad8085e49b 100644 --- a/app/screens/settings/notification_push/push_send.tsx +++ b/app/screens/settings/notification_push/push_send.tsx @@ -30,9 +30,9 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { }); type MobileSendPushProps = { - pushStatus: PushStatus; + pushStatus: UserNotifyPropsPushStatus; sendPushNotifications: boolean; - setMobilePushPref: (status: PushStatus) => void; + setMobilePushPref: (status: UserNotifyPropsPushStatus) => void; } const MobileSendPush = ({sendPushNotifications, pushStatus, setMobilePushPref}: MobileSendPushProps) => { const theme = useTheme(); diff --git a/app/screens/settings/notification_push/push_status.tsx b/app/screens/settings/notification_push/push_status.tsx index 4559b8b047..a114bc582b 100644 --- a/app/screens/settings/notification_push/push_status.tsx +++ b/app/screens/settings/notification_push/push_status.tsx @@ -16,8 +16,8 @@ const headerText = { }; type MobilePushStatusProps = { - pushStatus: PushStatus; - setMobilePushStatus: (status: PushStatus) => void; + pushStatus: UserNotifyPropsPushStatus; + setMobilePushStatus: (status: UserNotifyPropsPushStatus) => void; } const MobilePushStatus = ({pushStatus, setMobilePushStatus}: MobilePushStatusProps) => { const intl = useIntl(); diff --git a/app/screens/settings/notification_push/push_thread.tsx b/app/screens/settings/notification_push/push_thread.tsx index b3f0c88be9..f884855a76 100644 --- a/app/screens/settings/notification_push/push_thread.tsx +++ b/app/screens/settings/notification_push/push_thread.tsx @@ -17,7 +17,7 @@ const headerText = { type MobilePushThreadProps = { onMobilePushThreadChanged: (status: string) => void; - pushThread: PushStatus; + pushThread: UserNotifyPropsPushThreads; } const MobilePushThread = ({pushThread, onMobilePushThreadChanged}: MobilePushThreadProps) => { diff --git a/app/screens/settings/notifications/notifications.tsx b/app/screens/settings/notifications/notifications.tsx index 8dfa52963d..e7bbd92a4b 100644 --- a/app/screens/settings/notifications/notifications.tsx +++ b/app/screens/settings/notifications/notifications.tsx @@ -6,7 +6,7 @@ import {useIntl} from 'react-intl'; import {General, Screens} from '@constants'; import {t} from '@i18n'; -import {goToScreen} from '@screens/navigation'; +import {gotoSettingsScreen} from '@screens/settings/config'; import {getEmailInterval, getEmailIntervalTexts, getNotificationProps} from '@utils/user'; import SettingContainer from '../setting_container'; @@ -58,8 +58,7 @@ const Notifications = ({ 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); + gotoSettingsScreen(screen, title); }, [isCRTEnabled]); const goToNotificationSettingsPush = useCallback(() => { @@ -69,7 +68,7 @@ const Notifications = ({ defaultMessage: 'Push Notifications', }); - goToScreen(screen, title); + gotoSettingsScreen(screen, title); }, []); const goToNotificationAutoResponder = useCallback(() => { @@ -78,13 +77,13 @@ const Notifications = ({ id: 'notification_settings.auto_responder', defaultMessage: 'Automatic Replies', }); - goToScreen(screen, title); + gotoSettingsScreen(screen, title); }, []); const goToEmailSettings = useCallback(() => { const screen = Screens.SETTINGS_NOTIFICATION_EMAIL; const title = intl.formatMessage({id: 'notification_settings.email', defaultMessage: 'Email Notifications'}); - goToScreen(screen, title); + gotoSettingsScreen(screen, title); }, []); return ( diff --git a/types/screens/settings.d.ts b/types/screens/settings.d.ts index 500b68d5be..64c43e2728 100644 --- a/types/screens/settings.d.ts +++ b/types/screens/settings.d.ts @@ -1,4 +1,6 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -type PushStatus = 'away' | 'online' | 'offline' | 'none' | 'mention' | 'all' | 'ooo' | 'dnd' | 'default' ; +type UserNotifyPropsPush = typeof UserNotifyPropsPush.push; +type UserNotifyPropsPushStatus = typeof UserNotifyPropsPush.push_status; +type UserNotifyPropsPushThreads = typeof UserNotifyPropsPush.push_threads;