// Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import type { AudioDevice } from '@signalapp/ringrtc'; import React, { useCallback, useEffect, useMemo, useRef, useState, useId, } from 'react'; import { isNumber, noop, partition } from 'lodash'; import classNames from 'classnames'; import * as LocaleMatcher from '@formatjs/intl-localematcher'; import type { MutableRefObject, ReactNode } from 'react'; import { Button, ButtonVariant } from './Button'; import { ChatColorPicker } from './ChatColorPicker'; import { Checkbox } from './Checkbox'; import { WidthBreakpoint } from './_util'; import { ConfirmationDialog } from './ConfirmationDialog'; import { DisappearingTimeDialog } from './DisappearingTimeDialog'; import { PhoneNumberDiscoverability } from '../util/phoneNumberDiscoverability'; import { PhoneNumberSharingMode } from '../util/phoneNumberSharingMode'; import { Select } from './Select'; import { Spinner } from './Spinner'; import { getCustomColorStyle } from '../util/getCustomColorStyle'; import { DEFAULT_DURATIONS_IN_SECONDS, DEFAULT_DURATIONS_SET, format as formatExpirationTimer, } from '../util/expirationTimer'; import { DurationInSeconds } from '../util/durations'; import { focusableSelector } from '../util/focusableSelectors'; import { Modal } from './Modal'; import { SearchInput } from './SearchInput'; import { removeDiacritics } from '../util/removeDiacritics'; import { assertDev, strictAssert } from '../util/assert'; import { I18n } from './I18n'; import { FunSkinTonesList } from './fun/FunSkinTones'; import { emojiParentKeyConstant, type EmojiSkinTone } from './fun/data/emojis'; import { SettingsControl as Control, FlowingSettingsControl as FlowingControl, SettingsRadio, SettingsRow, } from './PreferencesUtil'; import { PreferencesBackups } from './PreferencesBackups'; import { PreferencesInternal } from './PreferencesInternal'; import { FunEmojiLocalizationProvider } from './fun/FunEmojiLocalizationProvider'; import { Avatar, AvatarSize } from './Avatar'; import { NavSidebar } from './NavSidebar'; import { SettingsPage } from '../types/Nav'; import type { MediaDeviceSettings } from '../types/Calling'; import type { ValidationResultType as BackupValidationResultType } from '../services/backups'; import type { AutoDownloadAttachmentType, NotificationSettingType, SentMediaQualitySettingType, ZoomFactorType, } from '../types/Storage.d'; import type { ThemeSettingType } from '../types/StorageUIKeys'; import type { AnyToast } from '../types/Toast'; import { ToastType } from '../types/Toast'; import type { ConversationType } from '../state/ducks/conversations'; import type { ConversationColorType, CustomColorType, DefaultConversationColorType, } from '../types/Colors'; import type { LocalizerType, SentMediaQualityType, ThemeType, } from '../types/Util'; import type { BackupMediaDownloadStatusType, BackupsSubscriptionType, BackupStatusType, } from '../types/backups'; import type { UnreadStats } from '../util/countUnreadStats'; import type { BadgeType } from '../badges/types'; import type { MessageCountBySchemaVersionType } from '../sql/Interface'; import type { MessageAttributesType } from '../model-types'; import { isBackupPage } from '../types/PreferencesBackupPage'; import type { PreferencesBackupPage } from '../types/PreferencesBackupPage'; import type { PromptOSAuthReasonType, PromptOSAuthResultType, } from '../util/os/promptOSAuthMain'; import type { DonationReceipt } from '../types/Donations'; import type { PreferredBadgeSelectorType } from '../state/selectors/badges'; import { EditChatFoldersPage } from './preferences/EditChatFoldersPage'; import { ChatFoldersPage } from './preferences/ChatFoldersPage'; import type { ChatFolderId, ChatFolderParams, ChatFolderRecord, } from '../types/ChatFolder'; import { CHAT_FOLDER_DEFAULTS, isChatFoldersEnabled, } from '../types/ChatFolder'; import type { GetConversationByIdType } from '../state/selectors/conversations'; type CheckboxChangeHandlerType = (value: boolean) => unknown; type SelectChangeHandlerType = (value: T) => unknown; export type PropsDataType = { conversations: ReadonlyArray; conversationSelector: GetConversationByIdType; // Settings accountEntropyPool: string | undefined; autoDownloadAttachment: AutoDownloadAttachmentType; backupFeatureEnabled: boolean; backupKeyViewed: boolean; backupLocalBackupsEnabled: boolean; localBackupFolder: string | undefined; cloudBackupStatus?: BackupStatusType; backupSubscriptionStatus: BackupsSubscriptionType; backupMediaDownloadStatus?: BackupMediaDownloadStatusType; pauseBackupMediaDownload: VoidFunction; cancelBackupMediaDownload: VoidFunction; resumeBackupMediaDownload: VoidFunction; blockedCount: number; customColors: Record; defaultConversationColor: DefaultConversationColorType; deviceName?: string; donationsFeatureEnabled: boolean; emojiSkinToneDefault: EmojiSkinTone; hasAudioNotifications?: boolean; hasAutoConvertEmoji: boolean; hasAutoDownloadUpdate: boolean; hasAutoLaunch: boolean | undefined; hasCallNotifications: boolean; hasCallRingtoneNotification: boolean; hasContentProtection: boolean | undefined; hasCountMutedConversations: boolean; hasHideMenuBar?: boolean; hasIncomingCallNotifications: boolean; hasLinkPreviews: boolean; hasMediaCameraPermissions: boolean | undefined; hasMediaPermissions: boolean | undefined; hasMessageAudio: boolean; hasMinimizeToAndStartInSystemTray: boolean | undefined; hasMinimizeToSystemTray: boolean | undefined; hasNotificationAttention: boolean; hasNotifications: boolean; hasReadReceipts: boolean; hasRelayCalls?: boolean; hasSpellCheck: boolean | undefined; hasStoriesDisabled: boolean; hasTextFormatting: boolean; hasTypingIndicators: boolean; page: SettingsPage; lastSyncTime?: number; notificationContent: NotificationSettingType; phoneNumber: string | undefined; selectedCamera?: string; selectedMicrophone?: AudioDevice; selectedSpeaker?: AudioDevice; sentMediaQualitySetting: SentMediaQualitySettingType; themeSetting: ThemeSettingType | undefined; universalExpireTimer: DurationInSeconds; whoCanFindMe: PhoneNumberDiscoverability; whoCanSeeMe: PhoneNumberSharingMode; zoomFactor: ZoomFactorType | undefined; // Localization availableLocales: ReadonlyArray; localeOverride: string | null | undefined; preferredSystemLocales: ReadonlyArray; resolvedLocale: string; // Other props badge: BadgeType | undefined; hasFailedStorySends: boolean; initialSpellCheckSetting: boolean; me: ConversationType; navTabsCollapsed: boolean; otherTabsUnreadStats: UnreadStats; preferredWidthFromStorage: number; shouldShowUpdateDialog: boolean; theme: ThemeType; // Limited support features isAutoDownloadUpdatesSupported: boolean; isAutoLaunchSupported: boolean; isContentProtectionNeeded: boolean; isContentProtectionSupported: boolean; isHideMenuBarSupported: boolean; isNotificationAttentionSupported: boolean; isSyncSupported: boolean; isSystemTraySupported: boolean; isMinimizeToAndStartInSystemTraySupported: boolean; isInternalUser: boolean; // Devices availableCameras: Array< Pick >; donationReceipts: ReadonlyArray; } & Omit; type PropsFunctionType = { // Render props renderDonationsPane: (options: { contentsRef: MutableRefObject; page: SettingsPage; setPage: (page: SettingsPage) => void; }) => JSX.Element; renderProfileEditor: (options: { contentsRef: MutableRefObject; }) => JSX.Element; renderToastManager: ( _: Readonly<{ containerWidthBreakpoint: WidthBreakpoint }> ) => JSX.Element; renderUpdateDialog: ( _: Readonly<{ containerWidthBreakpoint: WidthBreakpoint }> ) => JSX.Element; // Other props addCustomColor: (color: CustomColorType) => unknown; doDeleteAllData: () => unknown; editCustomColor: (colorId: string, color: CustomColorType) => unknown; exportLocalBackup: () => Promise; getMessageCountBySchemaVersion: () => Promise; getMessageSampleForSchemaVersion: ( version: number ) => Promise>; resumeBackupMediaDownload: () => void; pauseBackupMediaDownload: () => void; getConversationsWithCustomColor: (colorId: string) => Array; getPreferredBadge: PreferredBadgeSelectorType; makeSyncRequest: () => unknown; onStartUpdate: () => unknown; pickLocalBackupFolder: () => Promise; refreshCloudBackupStatus: () => void; refreshBackupSubscriptionStatus: () => void; removeCustomColor: (colorId: string) => unknown; removeCustomColorOnConversations: (colorId: string) => unknown; promptOSAuth: ( reason: PromptOSAuthReasonType ) => Promise; resetAllChatColors: () => unknown; resetDefaultChatColor: () => unknown; savePreferredLeftPaneWidth: (_: number) => void; setGlobalDefaultConversationColor: ( color: ConversationColorType, customColorData?: { id: string; value: CustomColorType; } ) => unknown; setPage: (page: SettingsPage) => unknown; showToast: (toast: AnyToast) => unknown; validateBackup: () => Promise; internalAddDonationReceipt: (receipt: DonationReceipt) => void; saveAttachmentToDisk: (options: { data: Uint8Array; name: string; baseDir?: string | undefined; }) => Promise<{ fullPath: string; name: string } | null>; generateDonationReceiptBlob: ( receipt: DonationReceipt, i18n: LocalizerType ) => Promise; // Change handlers onAudioNotificationsChange: CheckboxChangeHandlerType; onAutoConvertEmojiChange: CheckboxChangeHandlerType; onAutoDownloadAttachmentChange: ( setting: AutoDownloadAttachmentType ) => unknown; onAutoDownloadUpdateChange: CheckboxChangeHandlerType; onAutoLaunchChange: CheckboxChangeHandlerType; onBackupKeyViewedChange: (keyViewed: boolean) => void; onCallNotificationsChange: CheckboxChangeHandlerType; onCallRingtoneNotificationChange: CheckboxChangeHandlerType; onContentProtectionChange: CheckboxChangeHandlerType; onCountMutedConversationsChange: CheckboxChangeHandlerType; onEmojiSkinToneDefaultChange: (emojiSkinTone: EmojiSkinTone) => void; onHasStoriesDisabledChanged: SelectChangeHandlerType; onHideMenuBarChange: CheckboxChangeHandlerType; onIncomingCallNotificationsChange: CheckboxChangeHandlerType; onLastSyncTimeChange: (time: number) => unknown; onLocaleChange: (locale: string | null | undefined) => void; onMediaCameraPermissionsChange: CheckboxChangeHandlerType; onMediaPermissionsChange: CheckboxChangeHandlerType; onMessageAudioChange: CheckboxChangeHandlerType; onMinimizeToAndStartInSystemTrayChange: CheckboxChangeHandlerType; onMinimizeToSystemTrayChange: CheckboxChangeHandlerType; onNotificationAttentionChange: CheckboxChangeHandlerType; onNotificationContentChange: SelectChangeHandlerType; onNotificationsChange: CheckboxChangeHandlerType; onRelayCallsChange: CheckboxChangeHandlerType; onSelectedCameraChange: SelectChangeHandlerType; onSelectedMicrophoneChange: SelectChangeHandlerType; onSelectedSpeakerChange: SelectChangeHandlerType; onSentMediaQualityChange: SelectChangeHandlerType; onSpellCheckChange: CheckboxChangeHandlerType; onTextFormattingChange: CheckboxChangeHandlerType; onThemeChange: SelectChangeHandlerType; onToggleNavTabsCollapse: (navTabsCollapsed: boolean) => void; onUniversalExpireTimerChange: SelectChangeHandlerType; onWhoCanSeeMeChange: SelectChangeHandlerType; onWhoCanFindMeChange: SelectChangeHandlerType; onZoomFactorChange: SelectChangeHandlerType; // Localization i18n: LocalizerType; }; export type PropsType = PropsDataType & PropsFunctionType; export type PropsPreloadType = Omit; function isDonationsPage(page: SettingsPage): boolean { return ( page === SettingsPage.Donations || page === SettingsPage.DonationsDonateFlow || page === SettingsPage.DonationsReceiptList ); } enum LanguageDialog { Selection, Confirmation, } const DEFAULT_ZOOM_FACTORS = [ { text: '75%', value: 0.75, }, { text: '100%', value: 1, }, { text: '125%', value: 1.25, }, { text: '150%', value: 1.5, }, { text: '200%', value: 2, }, ]; export function Preferences({ conversations, conversationSelector, accountEntropyPool, addCustomColor, autoDownloadAttachment, availableCameras, availableLocales, availableMicrophones, availableSpeakers, backupFeatureEnabled, backupMediaDownloadStatus, pauseBackupMediaDownload, resumeBackupMediaDownload, cancelBackupMediaDownload, backupKeyViewed, backupSubscriptionStatus, backupLocalBackupsEnabled, badge, blockedCount, cloudBackupStatus, customColors, defaultConversationColor, deviceName = '', doDeleteAllData, donationsFeatureEnabled, editCustomColor, emojiSkinToneDefault, exportLocalBackup, getConversationsWithCustomColor, getMessageCountBySchemaVersion, getMessageSampleForSchemaVersion, getPreferredBadge, hasAudioNotifications, hasAutoConvertEmoji, hasAutoDownloadUpdate, hasAutoLaunch, hasCallNotifications, hasCallRingtoneNotification, hasContentProtection, hasCountMutedConversations, hasFailedStorySends, hasHideMenuBar, hasIncomingCallNotifications, hasLinkPreviews, hasMediaCameraPermissions, hasMediaPermissions, hasMessageAudio, hasMinimizeToAndStartInSystemTray, hasMinimizeToSystemTray, hasNotificationAttention, hasNotifications, hasReadReceipts, hasRelayCalls, hasSpellCheck, hasStoriesDisabled, hasTextFormatting, hasTypingIndicators, i18n, initialSpellCheckSetting, isAutoDownloadUpdatesSupported, isAutoLaunchSupported, isContentProtectionNeeded, isContentProtectionSupported, isHideMenuBarSupported, isNotificationAttentionSupported, isSyncSupported, isSystemTraySupported, isMinimizeToAndStartInSystemTraySupported, isInternalUser, lastSyncTime, localBackupFolder, makeSyncRequest, me, navTabsCollapsed, notificationContent, onAudioNotificationsChange, onAutoConvertEmojiChange, onAutoDownloadAttachmentChange, onAutoDownloadUpdateChange, onAutoLaunchChange, onBackupKeyViewedChange, onCallNotificationsChange, onCallRingtoneNotificationChange, onContentProtectionChange, onCountMutedConversationsChange, onEmojiSkinToneDefaultChange, onHasStoriesDisabledChanged, onHideMenuBarChange, onIncomingCallNotificationsChange, onLastSyncTimeChange, onLocaleChange, onMediaCameraPermissionsChange, onMediaPermissionsChange, onMessageAudioChange, onMinimizeToAndStartInSystemTrayChange, onMinimizeToSystemTrayChange, onNotificationAttentionChange, onNotificationContentChange, onNotificationsChange, onRelayCallsChange, onSelectedCameraChange, onSelectedMicrophoneChange, onSelectedSpeakerChange, onSentMediaQualityChange, onSpellCheckChange, onTextFormattingChange, onThemeChange, onToggleNavTabsCollapse, onUniversalExpireTimerChange, onWhoCanSeeMeChange, onWhoCanFindMeChange, onZoomFactorChange, otherTabsUnreadStats, page, phoneNumber = '', pickLocalBackupFolder, preferredSystemLocales, preferredWidthFromStorage, refreshCloudBackupStatus, refreshBackupSubscriptionStatus, removeCustomColor, removeCustomColorOnConversations, renderDonationsPane, renderProfileEditor, renderToastManager, renderUpdateDialog, promptOSAuth, resetAllChatColors, resetDefaultChatColor, resolvedLocale, savePreferredLeftPaneWidth, selectedCamera, selectedMicrophone, selectedSpeaker, sentMediaQualitySetting, setGlobalDefaultConversationColor, setPage, shouldShowUpdateDialog, showToast, localeOverride, theme, themeSetting, universalExpireTimer = DurationInSeconds.ZERO, validateBackup, whoCanFindMe, whoCanSeeMe, zoomFactor, donationReceipts, internalAddDonationReceipt, saveAttachmentToDisk, generateDonationReceiptBlob, }: PropsType): JSX.Element { const storiesId = useId(); const themeSelectId = useId(); const zoomSelectId = useId(); const languageId = useId(); const [confirmDelete, setConfirmDelete] = useState(false); const [confirmStoriesOff, setConfirmStoriesOff] = useState(false); const [confirmContentProtection, setConfirmContentProtection] = useState(false); const [showSyncFailed, setShowSyncFailed] = useState(false); const [nowSyncing, setNowSyncing] = useState(false); const [showDisappearingTimerDialog, setShowDisappearingTimerDialog] = useState(false); const [languageDialog, setLanguageDialog] = useState( null ); const [selectedLanguageLocale, setSelectedLanguageLocale] = useState< string | null | undefined >(localeOverride); const [languageSearchInput, setLanguageSearchInput] = useState(''); const [confirmPnpNotDiscoverable, setConfirmPnpNoDiscoverable] = useState(false); const [chatFolders, setChatFolders] = useState< ReadonlyArray >([]); const [editChatFolderPageId, setEditChatFolderPageId] = useState(null); const handleOpenEditChatFoldersPage = useCallback( (chatFolderId: ChatFolderId | null) => { setPage(SettingsPage.EditChatFolder); setEditChatFolderPageId(chatFolderId); }, [setPage] ); const handleCloseEditChatFoldersPage = useCallback(() => { setPage(SettingsPage.ChatFolders); setEditChatFolderPageId(null); }, [setPage]); const handleCreateChatFolder = useCallback((params: ChatFolderParams) => { setChatFolders(prev => { return [...prev, { ...params, id: String(prev.length) as ChatFolderId }]; }); }, []); const handleUpdateChatFolder = useCallback( (chatFolderId: ChatFolderId, chatFolderParams: ChatFolderParams) => { setChatFolders(prev => { return prev.map(chatFolder => { if (chatFolder.id === chatFolderId) { return { id: chatFolderId, ...chatFolderParams }; } return chatFolder; }); }); }, [] ); const handleDeleteChatFolder = useCallback((chatFolderId: ChatFolderId) => { setChatFolders(prev => { return prev.filter(chatFolder => { return chatFolder.id !== chatFolderId; }); }); }, []); function closeLanguageDialog() { setLanguageDialog(null); setSelectedLanguageLocale(localeOverride); } const shouldShowBackupsPage = backupFeatureEnabled || backupLocalBackupsEnabled; if (page === SettingsPage.Backups && !shouldShowBackupsPage) { setPage(SettingsPage.General); } if (isDonationsPage(page) && !donationsFeatureEnabled) { setPage(SettingsPage.General); } if (page === SettingsPage.Internal && !isInternalUser) { setPage(SettingsPage.General); } let maybeUpdateDialog: JSX.Element | undefined; if (shouldShowUpdateDialog) { maybeUpdateDialog = renderUpdateDialog({ containerWidthBreakpoint: WidthBreakpoint.Wide, }); } const onZoomSelectChange = useCallback( (value: string) => { const number = parseFloat(value); onZoomFactorChange(number as unknown as ZoomFactorType); }, [onZoomFactorChange] ); const onAudioInputSelectChange = useCallback( (value: string) => { if (value === 'undefined') { onSelectedMicrophoneChange(undefined); } else { onSelectedMicrophoneChange(availableMicrophones[parseInt(value, 10)]); } }, [onSelectedMicrophoneChange, availableMicrophones] ); const handleContentProtectionChange = useCallback( (value: boolean) => { if (value === true || !isContentProtectionNeeded) { onContentProtectionChange(value); } else { setConfirmContentProtection(true); } }, [onContentProtectionChange, isContentProtectionNeeded] ); const settingsPaneRef = useRef(null); useEffect(() => { const settingsPane = settingsPaneRef.current; if (!settingsPane) { return; } const elements = settingsPane.querySelectorAll< | HTMLAnchorElement | HTMLButtonElement | HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement >(focusableSelector); if (!elements.length) { return; } elements[0]?.focus(); }, [page]); const onAudioOutputSelectChange = useCallback( (value: string) => { if (value === 'undefined') { onSelectedSpeakerChange(undefined); } else { onSelectedSpeakerChange(availableSpeakers[parseInt(value, 10)]); } }, [onSelectedSpeakerChange, availableSpeakers] ); const localeDisplayNames = window.SignalContext.getLocaleDisplayNames(); const getLocaleDisplayName = useCallback( (inLocale: string, ofLocale: string): string => { const displayName = localeDisplayNames[inLocale]?.[ofLocale]; assertDev( displayName != null, `Locale display name in ${inLocale} of ${ofLocale} does not exist` ); return ( displayName ?? new Intl.DisplayNames(inLocale, { type: 'language', languageDisplay: 'standard', style: 'long', fallback: 'code', }).of(ofLocale) ); }, [localeDisplayNames] ); const localeSearchOptions = useMemo(() => { const collator = new Intl.Collator('en', { usage: 'sort' }); const availableLocalesOptions = availableLocales .map(locale => { const currentLocaleLabel = getLocaleDisplayName(resolvedLocale, locale); const matchingLocaleLabel = getLocaleDisplayName(locale, locale); return { locale, currentLocaleLabel, matchingLocaleLabel }; }) .sort((a, b) => { return collator.compare(a.locale, b.locale); }); const [localeOverrideMatches, localeOverrideNonMatches] = partition( availableLocalesOptions, option => { return option.locale === localeOverride; } ); const preferredSystemLocaleMatch = LocaleMatcher.match( preferredSystemLocales as Array, // bad types availableLocales as Array, // bad types 'en', { algorithm: 'best fit' } ); return [ ...localeOverrideMatches, { locale: null, currentLocaleLabel: i18n('icu:Preferences__Language__SystemLanguage'), matchingLocaleLabel: getLocaleDisplayName( preferredSystemLocaleMatch, preferredSystemLocaleMatch ), }, ...localeOverrideNonMatches, ]; }, [ i18n, availableLocales, resolvedLocale, localeOverride, preferredSystemLocales, getLocaleDisplayName, ]); const localeSearchResults = useMemo(() => { return localeSearchOptions.filter(option => { const input = removeDiacritics(languageSearchInput.trim().toLowerCase()); if (input === '') { return true; } function isMatch(value: string) { return removeDiacritics(value.toLowerCase()).includes(input); } return ( isMatch(option.currentLocaleLabel) || (option.matchingLocaleLabel && isMatch(option.matchingLocaleLabel)) ); }); }, [localeSearchOptions, languageSearchInput]); let content: JSX.Element | undefined; if (page === SettingsPage.Profile) { content = renderProfileEditor({ contentsRef: settingsPaneRef, }); } else if (page === SettingsPage.General) { const pageContents = ( <>
{i18n('icu:Preferences--phone-number')}
{phoneNumber}
{i18n('icu:Preferences--device-name')}
{deviceName}
{i18n('icu:Preferences--device-name__description')}
{isAutoLaunchSupported && ( )} {isHideMenuBarSupported && ( )} {isSystemTraySupported && ( <> {isMinimizeToAndStartInSystemTraySupported && ( )} )} {isAutoDownloadUpdatesSupported && ( )} ); content = ( ); } else if (isDonationsPage(page)) { content = renderDonationsPane({ contentsRef: settingsPaneRef, page, setPage, }); } else if (page === SettingsPage.Appearance) { let zoomFactors = DEFAULT_ZOOM_FACTORS; if ( isNumber(zoomFactor) && !zoomFactors.some(({ value }) => value === zoomFactor) ) { zoomFactors = [ ...zoomFactors, { text: `${Math.round(zoomFactor * 100)}%`, value: zoomFactor, }, ].sort((a, b) => a.value - b.value); } let localeText = ''; if (localeOverride !== undefined) { localeText = localeOverride != null ? getLocaleDisplayName(resolvedLocale, localeOverride) : i18n('icu:Preferences__Language__SystemLanguage'); } const pageContents = ( {localeText} } onClick={() => { // We haven't loaded the user's setting yet if (localeOverride === undefined) { return; } setLanguageDialog(LanguageDialog.Selection); }} /> {languageDialog === LanguageDialog.Selection && ( { setLanguageSearchInput(event.currentTarget.value); }} /> } modalFooter={ <> } > {localeSearchResults.length === 0 && (
{i18n('icu:Preferences__Language__NoResults', { searchTerm: languageSearchInput.trim(), })}
)} {localeSearchResults.map(option => { const id = `${languageId}:${option.locale ?? 'system'}`; const isSelected = option.locale === selectedLanguageLocale; return ( ); })}
)} {languageDialog === LanguageDialog.Confirmation && ( { onLocaleChange(selectedLanguageLocale); }, }, ]} > {i18n('icu:Preferences__LanguageModal__Restart__Description')} )} {i18n('icu:Preferences--theme')} } right={ } />
); content = ( ); } else if (page === SettingsPage.Chats) { let spellCheckDirtyText: string | undefined; if ( hasSpellCheck !== undefined && initialSpellCheckSetting !== hasSpellCheck ) { spellCheckDirtyText = hasSpellCheck ? i18n('icu:spellCheckWillBeEnabled') : i18n('icu:spellCheckWillBeDisabled'); } const lastSyncDate = new Date(lastSyncTime || 0); const pageContents = ( <> } label={i18n('icu:Preferences__auto-convert-emoji--title')} moduleClassName="Preferences__checkbox" name="autoConvertEmoji" onChange={onAutoConvertEmojiChange} /> } /> {isChatFoldersEnabled() && (
{i18n( 'icu:Preferences__ChatsPage__ChatFoldersSection__AddChatFolderItem__Title' )}
{i18n( 'icu:Preferences__ChatsPage__ChatFoldersSection__AddChatFolderItem__Description' )}
} right={null} onClick={() => setPage(SettingsPage.ChatFolders)} />
)} {isSyncSupported && (
{i18n('icu:sync')}
{i18n('icu:syncExplanation')}{' '} {i18n('icu:Preferences--lastSynced', { date: lastSyncDate.toLocaleDateString(), time: lastSyncDate.toLocaleTimeString(), })}
{showSyncFailed && (
{i18n('icu:syncFailed')}
)} } right={
} />
)} ); content = ( ); } else if (page === SettingsPage.Calls) { const pageContents = ( <> ({ text: localizeDefault(i18n, device.name), value: device.index, })) : [ { text: i18n( 'icu:callingDeviceSelection__select--no-device' ), value: 'undefined', }, ] } value={selectedMicrophone?.index} /> } right={
} /> { if ( value === String(universalExpireTimer) || value === '-1' ) { setShowDisappearingTimerDialog(true); return; } onUniversalExpireTimerChange(parseInt(value, 10)); }} options={DEFAULT_DURATIONS_IN_SECONDS.map(seconds => { const text = formatExpirationTimer(i18n, seconds, { capitalizeOff: true, }); return { value: seconds, text, }; }).concat([ { value: isCustomDisappearingMessageValue ? universalExpireTimer : DurationInSeconds.fromSeconds(-1), text: isCustomDisappearingMessageValue ? formatExpirationTimer(i18n, universalExpireTimer) : i18n('icu:selectedCustomDisappearingTimeOption'), }, ])} value={universalExpireTimer} />
{isContentProtectionSupported && ( )} {confirmContentProtection ? ( onContentProtectionChange(false), style: 'negative', text: i18n( 'icu:Preferences__content-protection__modal--disable' ), }, ]} i18n={i18n} onClose={() => { setConfirmContentProtection(false); }} title={i18n('icu:Preferences__content-protection__modal--title')} > {i18n('icu:Preferences__content-protection__modal--body')} ) : null}
{hasStoriesDisabled ? ( ) : ( )}
{i18n('icu:clearDataHeader')}
{i18n('icu:clearDataExplanation')}
{confirmDelete ? ( { setConfirmDelete(false); }} title={i18n('icu:deleteAllDataHeader')} > {i18n('icu:deleteAllDataBody')} ) : null} {confirmStoriesOff ? ( onHasStoriesDisabledChanged(true), style: 'negative', text: i18n('icu:Preferences__turn-stories-off--action'), }, ]} i18n={i18n} onClose={() => { setConfirmStoriesOff(false); }} > {i18n('icu:Preferences__turn-stories-off--body')} ) : null} ); content = ( ); } else if (page === SettingsPage.DataUsage) { const pageContents = ( <> onAutoDownloadAttachmentChange({ ...autoDownloadAttachment, photos: newValue, }) } /> onAutoDownloadAttachmentChange({ ...autoDownloadAttachment, videos: newValue, }) } /> onAutoDownloadAttachmentChange({ ...autoDownloadAttachment, audio: newValue, }) } /> onAutoDownloadAttachmentChange({ ...autoDownloadAttachment, documents: newValue, }) } />
{i18n('icu:Preferences__media-auto-download__description')}
{i18n('icu:Preferences__sent-media-quality')}
{i18n('icu:Preferences__sent-media-quality__description')}