diff --git a/app/constants/screens.ts b/app/constants/screens.ts index 977721e800..b4d60e4b9c 100644 --- a/app/constants/screens.ts +++ b/app/constants/screens.ts @@ -49,6 +49,7 @@ export const SETTINGS = 'Settings'; export const SETTINGS_ADVANCED = 'SettingsAdvanced'; export const SETTINGS_DISPLAY = 'SettingsDisplay'; export const SETTINGS_DISPLAY_CLOCK = 'SettingsDisplayClock'; +export const SETTINGS_DISPLAY_CRT = 'SettingsDisplayCRT'; export const SETTINGS_DISPLAY_THEME = 'SettingsDisplayTheme'; export const SETTINGS_DISPLAY_TIMEZONE = 'SettingsDisplayTimezone'; export const SETTINGS_DISPLAY_TIMEZONE_SELECT = 'SettingsDisplayTimezoneSelect'; @@ -116,6 +117,7 @@ export default { SETTINGS_ADVANCED, SETTINGS_DISPLAY, SETTINGS_DISPLAY_CLOCK, + SETTINGS_DISPLAY_CRT, SETTINGS_DISPLAY_THEME, SETTINGS_DISPLAY_TIMEZONE, SETTINGS_DISPLAY_TIMEZONE_SELECT, diff --git a/app/queries/servers/thread.ts b/app/queries/servers/thread.ts index b449e544ff..19abdcfb89 100644 --- a/app/queries/servers/thread.ts +++ b/app/queries/servers/thread.ts @@ -5,9 +5,9 @@ import {Database, Q, Query} from '@nozbe/watermelondb'; import {combineLatest, of as of$, Observable} from 'rxjs'; import {map, switchMap, distinctUntilChanged} from 'rxjs/operators'; -import {Preferences} from '@constants'; +import {Config, Preferences} from '@constants'; import {MM_TABLES} from '@constants/database'; -import {processIsCRTEnabled} from '@utils/thread'; +import {processIsCRTAllowed, processIsCRTEnabled} from '@utils/thread'; import {queryPreferencesByCategoryAndName} from './preference'; import {getConfig, observeConfigValue} from './system'; @@ -40,6 +40,12 @@ export const getTeamThreadsSyncData = async (database: Database, teamId: string) return result?.[0]; }; +export const observeCRTUserPreferenceDisplay = (database: Database) => { + return observeConfigValue(database, 'CollapsedThreads').pipe( + switchMap((value) => of$(processIsCRTAllowed(value) && value !== Config.ALWAYS_ON)), + ); +}; + export const observeIsCRTEnabled = (database: Database) => { const cfgValue = observeConfigValue(database, 'CollapsedThreads'); const featureFlag = observeConfigValue(database, 'FeatureFlagCollapsedThreads'); diff --git a/app/screens/index.tsx b/app/screens/index.tsx index 42986a4988..051d446f13 100644 --- a/app/screens/index.tsx +++ b/app/screens/index.tsx @@ -182,6 +182,9 @@ Navigation.setLazyComponentRegistrator((screenName) => { case Screens.SETTINGS_DISPLAY_CLOCK: screen = withServerDatabase(require('@screens/settings/display_clock').default); break; + case Screens.SETTINGS_DISPLAY_CRT: + screen = withServerDatabase(require('@screens/settings/display_crt').default); + break; case Screens.SETTINGS_DISPLAY_THEME: screen = withServerDatabase(require('@screens/settings/display_theme').default); break; diff --git a/app/screens/settings/config.ts b/app/screens/settings/config.ts index 9e65b269c5..106505c2e5 100644 --- a/app/screens/settings/config.ts +++ b/app/screens/settings/config.ts @@ -107,6 +107,12 @@ export const DisplayOptionConfig: Record = { icon: 'clock-outline', testID: 'display_settings.clock', }, + crt: { + defaultMessage: 'Collapsed Reply Threads', + i18nId: t('mobile.display_settings.crt'), + icon: 'message-text-outline', + testID: 'display_settings.crt', + }, theme: { defaultMessage: 'Theme', i18nId: t('mobile.display_settings.theme'), diff --git a/app/screens/settings/display/display.tsx b/app/screens/settings/display/display.tsx index d957c521cc..8612773eb5 100644 --- a/app/screens/settings/display/display.tsx +++ b/app/screens/settings/display/display.tsx @@ -19,6 +19,17 @@ import SettingItem from '../setting_item'; import type UserModel from '@typings/database/models/servers/user'; import type {AvailableScreens} from '@typings/screens/navigation'; +const CRT_FORMAT = [ + { + id: t('display_settings.crt.on'), + defaultMessage: 'On', + }, + { + id: t('display_settings.crt.off'), + defaultMessage: 'Off', + }, +]; + const TIME_FORMAT = [ { id: t('display_settings.clock.standard'), @@ -45,10 +56,13 @@ type DisplayProps = { componentId: AvailableScreens; currentUser: UserModel; hasMilitaryTimeFormat: boolean; + isCRTEnabled: boolean; + isCRTSwitchEnabled: boolean; isThemeSwitchingEnabled: boolean; isTimezoneEnabled: boolean; } -const Display = ({componentId, currentUser, hasMilitaryTimeFormat, isThemeSwitchingEnabled, isTimezoneEnabled}: DisplayProps) => { + +const Display = ({componentId, currentUser, hasMilitaryTimeFormat, isCRTEnabled, isCRTSwitchEnabled, isThemeSwitchingEnabled, isTimezoneEnabled}: DisplayProps) => { const intl = useIntl(); const theme = useTheme(); const timezone = useMemo(() => getUserTimezoneProps(currentUser), [currentUser.timezone]); @@ -71,6 +85,12 @@ const Display = ({componentId, currentUser, hasMilitaryTimeFormat, isThemeSwitch gotoSettingsScreen(screen, title); }); + const goToCRTSettings = preventDoubleTap(() => { + const screen = Screens.SETTINGS_DISPLAY_CRT; + const title = intl.formatMessage({id: 'display_settings.crt', defaultMessage: 'Collapsed Reply Threads'}); + gotoSettingsScreen(screen, title); + }); + useAndroidHardwareBackHandler(componentId, () => { popTopScreen(componentId); }); @@ -99,6 +119,14 @@ const Display = ({componentId, currentUser, hasMilitaryTimeFormat, isThemeSwitch testID='display_settings.timezone.option' /> )} + {isCRTSwitchEnabled && ( + + )} ); }; diff --git a/app/screens/settings/display/index.tsx b/app/screens/settings/display/index.tsx index 2ac9c35ca8..4bcedfaaf6 100644 --- a/app/screens/settings/display/index.tsx +++ b/app/screens/settings/display/index.tsx @@ -10,6 +10,7 @@ import {Preferences} from '@constants'; import {getPreferenceAsBool} from '@helpers/api/preference'; import {queryPreferencesByCategoryAndName} from '@queries/servers/preference'; import {observeAllowedThemesKeys, observeConfigBooleanValue} from '@queries/servers/system'; +import {observeCRTUserPreferenceDisplay, observeIsCRTEnabled} from '@queries/servers/thread'; import {observeCurrentUser} from '@queries/servers/user'; import DisplaySettings from './display'; @@ -31,6 +32,8 @@ const enhanced = withObservables([], ({database}: WithDatabaseArgs) => { return { isTimezoneEnabled, isThemeSwitchingEnabled, + isCRTEnabled: observeIsCRTEnabled(database), + isCRTSwitchEnabled: observeCRTUserPreferenceDisplay(database), hasMilitaryTimeFormat: queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS). observeWithColumns(['value']).pipe( switchMap( diff --git a/app/screens/settings/display_crt/display_crt.tsx b/app/screens/settings/display_crt/display_crt.tsx new file mode 100644 index 0000000000..a9110dd6fc --- /dev/null +++ b/app/screens/settings/display_crt/display_crt.tsx @@ -0,0 +1,74 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +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 useAndroidHardwareBackHandler from '@hooks/android_back_handler'; +import useBackNavigation from '@hooks/navigate_back'; +import {t} from '@i18n'; +import {popTopScreen} from '@screens/navigation'; + +import SettingBlock from '../setting_block'; +import SettingContainer from '../setting_container'; +import SettingOption from '../setting_option'; +import SettingSeparator from '../settings_separator'; + +import type {AvailableScreens} from '@typings/screens/navigation'; + +const crtDescription = { + id: t('settings_display.crt.desc'), + defaultMessage: 'When enabled, reply messages are not shown in the channel and you\'ll be notified about threads you\'re following in the "Threads" view.', +}; + +type Props = { + componentId: AvailableScreens; + currentUserId: string; + isCRTEnabled: boolean; +} + +const DisplayCRT = ({componentId, currentUserId, isCRTEnabled}: Props) => { + const [isEnabled, setIsEnabled] = useState(isCRTEnabled); + const serverUrl = useServerUrl(); + const intl = useIntl(); + + const close = () => popTopScreen(componentId); + + const saveCRTPreference = useCallback(() => { + if (isCRTEnabled !== isEnabled) { + const crtPreference: PreferenceType = { + category: Preferences.CATEGORY_DISPLAY_SETTINGS, + name: Preferences.COLLAPSED_REPLY_THREADS, + user_id: currentUserId, + value: isEnabled ? Preferences.COLLAPSED_REPLY_THREADS_ON : Preferences.COLLAPSED_REPLY_THREADS_OFF, + }; + savePreference(serverUrl, [crtPreference]); + } + close(); + }, [isEnabled, isCRTEnabled, serverUrl]); + + useBackNavigation(saveCRTPreference); + useAndroidHardwareBackHandler(componentId, saveCRTPreference); + + return ( + + + + + + + ); +}; + +export default DisplayCRT; diff --git a/app/screens/settings/display_crt/index.ts b/app/screens/settings/display_crt/index.ts new file mode 100644 index 0000000000..79c69ff8f9 --- /dev/null +++ b/app/screens/settings/display_crt/index.ts @@ -0,0 +1,21 @@ +// 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 {observeCurrentUserId} from '@queries/servers/system'; +import {observeIsCRTEnabled} from '@queries/servers/thread'; + +import DisplayCRT from './display_crt'; + +import type {WithDatabaseArgs} from '@typings/database/database'; + +const enhanced = withObservables([], ({database}: WithDatabaseArgs) => { + return { + currentUserId: observeCurrentUserId(database), + isCRTEnabled: observeIsCRTEnabled(database), + }; +}); + +export default withDatabase(enhanced(DisplayCRT)); diff --git a/app/utils/thread/index.ts b/app/utils/thread/index.ts index 697a45ef35..7a4f016eb2 100644 --- a/app/utils/thread/index.ts +++ b/app/utils/thread/index.ts @@ -7,6 +7,10 @@ import {isMinimumServerVersion} from '@utils/helpers'; import type PreferenceModel from '@typings/database/models/servers/preference'; +export function processIsCRTAllowed(configValue?: string): boolean { + return Boolean(configValue) && configValue !== Config.DISABLED; +} + export function processIsCRTEnabled(preferences: PreferenceModel[]|PreferenceType[], configValue?: string, featureFlag?: string, version?: string): boolean { let preferenceDefault = Preferences.COLLAPSED_REPLY_THREADS_OFF; if (configValue === Config.DEFAULT_ON) { diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index f7ee276f50..6e0a16d089 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -235,6 +235,9 @@ "display_settings.clock.military": "24-hour", "display_settings.clock.standard": "12-hour", "display_settings.clockDisplay": "Clock Display", + "display_settings.crt": "Collapsed Reply Threads", + "display_settings.crt.off": "Off", + "display_settings.crt.on": "On", "display_settings.theme": "Theme", "display_settings.timezone": "Timezone", "display_settings.tz.auto": "Auto", @@ -481,6 +484,7 @@ "mobile.custom_status.modal_confirm": "Done", "mobile.direct_message.error": "We couldn't open a DM with {displayName}.", "mobile.display_settings.clockDisplay": "Clock Display", + "mobile.display_settings.crt": "Collapsed Reply Threads", "mobile.display_settings.theme": "Theme", "mobile.display_settings.timezone": "Timezone", "mobile.document_preview.failed_description": "An error occurred while opening the document. Please make sure you have a {fileType} viewer installed and try again.\n", @@ -833,6 +837,8 @@ "settings_display.clock.mz.desc": "Example: 16:00", "settings_display.clock.normal.desc": "Example: 4:00 PM", "settings_display.clock.standard": "12-hour clock", + "settings_display.crt.desc": "When enabled, reply messages are not shown in the channel and you'll be notified about threads you're following in the \"Threads\" view.", + "settings_display.crt.label": "Collapsed Reply Threads", "settings_display.custom_theme": "Custom Theme", "settings_display.timezone.automatically": "Set automatically", "settings_display.timezone.manual": "Change timezone",