signal-desktop/ts/components/Preferences.tsx

1701 lines
55 KiB
TypeScript
Raw Normal View History

2023-01-03 19:55:46 +00:00
// Copyright 2021 Signal Messenger, LLC
2021-08-18 20:08:14 +00:00
// SPDX-License-Identifier: AGPL-3.0-only
import type { AudioDevice } from '@signalapp/ringrtc';
import type { ReactNode } from 'react';
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
2023-11-06 21:19:23 +00:00
import { noop, partition } from 'lodash';
2021-08-18 20:08:14 +00:00
import classNames from 'classnames';
2023-02-23 21:32:19 +00:00
import uuid from 'uuid';
2023-11-06 21:19:23 +00:00
import * as LocaleMatcher from '@formatjs/intl-localematcher';
2021-08-18 20:08:14 +00:00
import type { MediaDeviceSettings } from '../types/Calling';
import type {
2021-08-18 20:08:14 +00:00
NotificationSettingType,
SentMediaQualitySettingType,
ZoomFactorType,
2021-08-18 20:08:14 +00:00
} from '../types/Storage.d';
import type { ThemeSettingType } from '../types/StorageUIKeys';
import type { ConversationType } from '../state/ducks/conversations';
import type {
2021-08-18 20:08:14 +00:00
ConversationColorType,
CustomColorType,
DefaultConversationColorType,
} from '../types/Colors';
import type {
LocalizerType,
SentMediaQualityType,
ThemeType,
} from '../types/Util';
import type { ExecuteMenuRoleType } from './TitleBarContainer';
import { Button, ButtonVariant } from './Button';
import { ChatColorPicker } from './ChatColorPicker';
import { Checkbox } from './Checkbox';
import {
CircleCheckbox,
Variant as CircleCheckboxVariant,
} from './CircleCheckbox';
import { ConfirmationDialog } from './ConfirmationDialog';
import { DisappearingTimeDialog } from './DisappearingTimeDialog';
2021-08-18 20:08:14 +00:00
import { PhoneNumberDiscoverability } from '../util/phoneNumberDiscoverability';
import { PhoneNumberSharingMode } from '../util/phoneNumberSharingMode';
import { Select } from './Select';
import { Spinner } from './Spinner';
import { TitleBarContainer } from './TitleBarContainer';
2021-08-18 20:08:14 +00:00
import { getCustomColorStyle } from '../util/getCustomColorStyle';
import {
DEFAULT_DURATIONS_IN_SECONDS,
DEFAULT_DURATIONS_SET,
format as formatExpirationTimer,
} from '../util/expirationTimer';
2022-11-16 20:18:02 +00:00
import { DurationInSeconds } from '../util/durations';
2021-09-17 22:24:21 +00:00
import { useEscapeHandling } from '../hooks/useEscapeHandling';
import { useUniqueId } from '../hooks/useUniqueId';
import { useTheme } from '../hooks/useTheme';
2023-05-04 19:34:52 +00:00
import { focusableSelectors } from '../util/focusableSelectors';
2023-11-06 21:19:23 +00:00
import { Modal } from './Modal';
import { SearchInput } from './SearchInput';
import { removeDiacritics } from '../util/removeDiacritics';
import { assertDev } from '../util/assert';
2021-08-18 20:08:14 +00:00
type CheckboxChangeHandlerType = (value: boolean) => unknown;
type SelectChangeHandlerType<T = string | number> = (value: T) => unknown;
2022-07-20 00:47:05 +00:00
export type PropsDataType = {
2021-08-18 20:08:14 +00:00
// Settings
blockedCount: number;
customColors: Record<string, CustomColorType>;
defaultConversationColor: DefaultConversationColorType;
deviceName?: string;
hasAudioNotifications?: boolean;
2023-12-18 23:22:46 +00:00
hasAutoConvertEmoji: boolean;
hasAutoDownloadUpdate: boolean;
2021-08-18 20:08:14 +00:00
hasAutoLaunch: boolean;
hasCallNotifications: boolean;
hasCallRingtoneNotification: boolean;
hasCountMutedConversations: boolean;
hasHideMenuBar?: boolean;
hasIncomingCallNotifications: boolean;
hasLinkPreviews: boolean;
hasMediaCameraPermissions: boolean;
hasMediaPermissions: boolean;
hasMessageAudio: boolean;
2021-08-18 20:08:14 +00:00
hasMinimizeToAndStartInSystemTray: boolean;
hasMinimizeToSystemTray: boolean;
hasNotificationAttention: boolean;
hasNotifications: boolean;
hasReadReceipts: boolean;
hasRelayCalls?: boolean;
hasSpellCheck: boolean;
hasStoriesDisabled: boolean;
hasTextFormatting: boolean;
2021-08-18 20:08:14 +00:00
hasTypingIndicators: boolean;
lastSyncTime?: number;
notificationContent: NotificationSettingType;
selectedCamera?: string;
selectedMicrophone?: AudioDevice;
selectedSpeaker?: AudioDevice;
sentMediaQualitySetting: SentMediaQualitySettingType;
2021-08-18 20:08:14 +00:00
themeSetting: ThemeSettingType;
2022-11-16 20:18:02 +00:00
universalExpireTimer: DurationInSeconds;
2021-08-18 20:08:14 +00:00
whoCanFindMe: PhoneNumberDiscoverability;
whoCanSeeMe: PhoneNumberSharingMode;
zoomFactor: ZoomFactorType;
2023-11-06 21:19:23 +00:00
// Localization
availableLocales: ReadonlyArray<string>;
localeOverride: string | null;
preferredSystemLocales: ReadonlyArray<string>;
resolvedLocale: string;
2022-07-20 00:47:05 +00:00
// Other props
hasCustomTitleBar: boolean;
initialSpellCheckSetting: boolean;
2022-07-20 00:47:05 +00:00
// Limited support features
isAutoDownloadUpdatesSupported: boolean;
isAutoLaunchSupported: boolean;
isHideMenuBarSupported: boolean;
isNotificationAttentionSupported: boolean;
isPhoneNumberSharingSupported: boolean;
isSyncSupported: boolean;
isSystemTraySupported: boolean;
2022-09-06 22:09:52 +00:00
isMinimizeToAndStartInSystemTraySupported: boolean;
2022-07-20 00:47:05 +00:00
availableCameras: Array<
Pick<MediaDeviceInfo, 'deviceId' | 'groupId' | 'kind' | 'label'>
>;
} & Omit<MediaDeviceSettings, 'availableCameras'>;
type PropsFunctionType = {
2021-08-18 20:08:14 +00:00
// Other props
addCustomColor: (color: CustomColorType) => unknown;
2021-08-24 21:00:56 +00:00
closeSettings: () => unknown;
2021-08-18 20:08:14 +00:00
doDeleteAllData: () => unknown;
doneRendering: () => unknown;
2021-08-18 20:08:14 +00:00
editCustomColor: (colorId: string, color: CustomColorType) => unknown;
2022-07-20 00:47:05 +00:00
executeMenuRole: ExecuteMenuRoleType;
2021-08-18 20:08:14 +00:00
getConversationsWithCustomColor: (
colorId: string
) => Promise<Array<ConversationType>>;
makeSyncRequest: () => unknown;
removeCustomColor: (colorId: string) => unknown;
removeCustomColorOnConversations: (colorId: string) => unknown;
resetAllChatColors: () => unknown;
resetDefaultChatColor: () => unknown;
setGlobalDefaultConversationColor: (
color: ConversationColorType,
customColorData?: {
id: string;
value: CustomColorType;
}
) => unknown;
// Change handlers
onAudioNotificationsChange: CheckboxChangeHandlerType;
2023-12-18 23:22:46 +00:00
onAutoConvertEmojiChange: CheckboxChangeHandlerType;
onAutoDownloadUpdateChange: CheckboxChangeHandlerType;
2021-08-18 20:08:14 +00:00
onAutoLaunchChange: CheckboxChangeHandlerType;
onCallNotificationsChange: CheckboxChangeHandlerType;
onCallRingtoneNotificationChange: CheckboxChangeHandlerType;
onCountMutedConversationsChange: CheckboxChangeHandlerType;
onHasStoriesDisabledChanged: SelectChangeHandlerType<boolean>;
2021-08-18 20:08:14 +00:00
onHideMenuBarChange: CheckboxChangeHandlerType;
onIncomingCallNotificationsChange: CheckboxChangeHandlerType;
onLastSyncTimeChange: (time: number) => unknown;
2023-11-06 21:19:23 +00:00
onLocaleChange: (locale: string | null) => void;
2021-08-18 20:08:14 +00:00
onMediaCameraPermissionsChange: CheckboxChangeHandlerType;
onMediaPermissionsChange: CheckboxChangeHandlerType;
onMessageAudioChange: CheckboxChangeHandlerType;
2021-08-18 20:08:14 +00:00
onMinimizeToAndStartInSystemTrayChange: CheckboxChangeHandlerType;
onMinimizeToSystemTrayChange: CheckboxChangeHandlerType;
onNotificationAttentionChange: CheckboxChangeHandlerType;
onNotificationContentChange: SelectChangeHandlerType<NotificationSettingType>;
onNotificationsChange: CheckboxChangeHandlerType;
onRelayCallsChange: CheckboxChangeHandlerType;
onSelectedCameraChange: SelectChangeHandlerType<string | undefined>;
onSelectedMicrophoneChange: SelectChangeHandlerType<AudioDevice | undefined>;
onSelectedSpeakerChange: SelectChangeHandlerType<AudioDevice | undefined>;
onSentMediaQualityChange: SelectChangeHandlerType<SentMediaQualityType>;
2021-08-18 20:08:14 +00:00
onSpellCheckChange: CheckboxChangeHandlerType;
onTextFormattingChange: CheckboxChangeHandlerType;
2021-08-18 20:08:14 +00:00
onThemeChange: SelectChangeHandlerType<ThemeType>;
onUniversalExpireTimerChange: SelectChangeHandlerType<number>;
2023-02-23 21:32:19 +00:00
onWhoCanSeeMeChange: SelectChangeHandlerType<PhoneNumberSharingMode>;
onWhoCanFindMeChange: SelectChangeHandlerType<PhoneNumberDiscoverability>;
2021-08-18 20:08:14 +00:00
onZoomFactorChange: SelectChangeHandlerType<ZoomFactorType>;
// Localization
i18n: LocalizerType;
2022-07-20 00:47:05 +00:00
};
export type PropsType = PropsDataType & PropsFunctionType;
2021-08-18 20:08:14 +00:00
export type PropsPreloadType = Omit<PropsType, 'i18n'>;
2021-08-18 20:08:14 +00:00
enum Page {
// Accessible through left nav
General = 'General',
Appearance = 'Appearance',
Chats = 'Chats',
Calls = 'Calls',
Notifications = 'Notifications',
Privacy = 'Privacy',
// Sub pages
ChatColor = 'ChatColor',
2023-02-23 21:32:19 +00:00
PNP = 'PNP',
2021-08-18 20:08:14 +00:00
}
2023-11-06 21:19:23 +00:00
enum LanguageDialog {
Selection,
Confirmation,
}
2021-09-02 23:29:16 +00:00
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,
},
];
2022-11-18 00:45:19 +00:00
export function Preferences({
2021-08-18 20:08:14 +00:00
addCustomColor,
availableCameras,
2023-11-06 21:19:23 +00:00
availableLocales,
2021-08-18 20:08:14 +00:00
availableMicrophones,
availableSpeakers,
blockedCount,
2021-08-24 21:00:56 +00:00
closeSettings,
2021-08-18 20:08:14 +00:00
customColors,
defaultConversationColor,
deviceName = '',
doDeleteAllData,
doneRendering,
2021-08-18 20:08:14 +00:00
editCustomColor,
executeMenuRole,
2021-08-18 20:08:14 +00:00
getConversationsWithCustomColor,
hasAudioNotifications,
2023-12-18 23:22:46 +00:00
hasAutoConvertEmoji,
hasAutoDownloadUpdate,
2021-08-18 20:08:14 +00:00
hasAutoLaunch,
hasCallNotifications,
hasCallRingtoneNotification,
hasCountMutedConversations,
hasHideMenuBar,
hasIncomingCallNotifications,
hasLinkPreviews,
hasMediaCameraPermissions,
hasMediaPermissions,
hasMessageAudio,
2021-08-18 20:08:14 +00:00
hasMinimizeToAndStartInSystemTray,
hasMinimizeToSystemTray,
hasNotificationAttention,
hasNotifications,
hasReadReceipts,
hasRelayCalls,
hasSpellCheck,
hasStoriesDisabled,
hasTextFormatting,
2021-08-18 20:08:14 +00:00
hasTypingIndicators,
i18n,
initialSpellCheckSetting,
isAutoDownloadUpdatesSupported,
2021-08-18 20:08:14 +00:00
isAutoLaunchSupported,
isHideMenuBarSupported,
2021-08-19 01:04:38 +00:00
isPhoneNumberSharingSupported,
2021-08-18 20:08:14 +00:00
isNotificationAttentionSupported,
isSyncSupported,
isSystemTraySupported,
2022-09-06 22:09:52 +00:00
isMinimizeToAndStartInSystemTraySupported,
2022-07-05 16:44:53 +00:00
hasCustomTitleBar,
2021-08-18 20:08:14 +00:00
lastSyncTime,
makeSyncRequest,
notificationContent,
onAudioNotificationsChange,
2023-12-18 23:22:46 +00:00
onAutoConvertEmojiChange,
onAutoDownloadUpdateChange,
2021-08-18 20:08:14 +00:00
onAutoLaunchChange,
onCallNotificationsChange,
onCallRingtoneNotificationChange,
onCountMutedConversationsChange,
onHasStoriesDisabledChanged,
2021-08-18 20:08:14 +00:00
onHideMenuBarChange,
onIncomingCallNotificationsChange,
onLastSyncTimeChange,
2023-11-06 21:19:23 +00:00
onLocaleChange,
2021-08-18 20:08:14 +00:00
onMediaCameraPermissionsChange,
onMediaPermissionsChange,
onMessageAudioChange,
2021-08-18 20:08:14 +00:00
onMinimizeToAndStartInSystemTrayChange,
onMinimizeToSystemTrayChange,
onNotificationAttentionChange,
onNotificationContentChange,
onNotificationsChange,
onRelayCallsChange,
onSelectedCameraChange,
onSelectedMicrophoneChange,
onSelectedSpeakerChange,
onSentMediaQualityChange,
2021-08-18 20:08:14 +00:00
onSpellCheckChange,
onTextFormattingChange,
2021-08-18 20:08:14 +00:00
onThemeChange,
onUniversalExpireTimerChange,
2023-02-23 21:32:19 +00:00
onWhoCanSeeMeChange,
onWhoCanFindMeChange,
2021-08-18 20:08:14 +00:00
onZoomFactorChange,
2023-11-06 21:19:23 +00:00
preferredSystemLocales,
2021-08-18 20:08:14 +00:00
removeCustomColor,
removeCustomColorOnConversations,
resetAllChatColors,
resetDefaultChatColor,
2023-11-06 21:19:23 +00:00
resolvedLocale,
2021-08-18 20:08:14 +00:00
selectedCamera,
selectedMicrophone,
selectedSpeaker,
sentMediaQualitySetting,
2021-08-18 20:08:14 +00:00
setGlobalDefaultConversationColor,
2023-11-06 21:19:23 +00:00
localeOverride,
2021-08-18 20:08:14 +00:00
themeSetting,
2022-11-16 20:18:02 +00:00
universalExpireTimer = DurationInSeconds.ZERO,
2021-08-18 20:08:14 +00:00
whoCanFindMe,
whoCanSeeMe,
zoomFactor,
2022-11-18 00:45:19 +00:00
}: PropsType): JSX.Element {
2022-07-20 00:47:05 +00:00
const storiesId = useUniqueId();
const themeSelectId = useUniqueId();
const zoomSelectId = useUniqueId();
2023-11-06 21:19:23 +00:00
const languageId = useUniqueId();
2021-08-24 20:57:34 +00:00
const [confirmDelete, setConfirmDelete] = useState(false);
2022-10-03 23:56:10 +00:00
const [confirmStoriesOff, setConfirmStoriesOff] = useState(false);
2021-08-18 20:08:14 +00:00
const [page, setPage] = useState<Page>(Page.General);
const [showSyncFailed, setShowSyncFailed] = useState(false);
const [nowSyncing, setNowSyncing] = useState(false);
2021-11-11 22:43:05 +00:00
const [showDisappearingTimerDialog, setShowDisappearingTimerDialog] =
useState(false);
2023-11-06 21:19:23 +00:00
const [languageDialog, setLanguageDialog] = useState<LanguageDialog | null>(
null
);
const [selectedLanguageLocale, setSelectedLanguageLocale] = useState<
string | null
>(localeOverride);
const [languageSearchInput, setLanguageSearchInput] = useState('');
const theme = useTheme();
2021-08-18 20:08:14 +00:00
2023-11-06 21:19:23 +00:00
function closeLanguageDialog() {
setLanguageDialog(null);
setSelectedLanguageLocale(localeOverride);
}
useEffect(() => {
doneRendering();
}, [doneRendering]);
2023-11-06 21:19:23 +00:00
useEscapeHandling(() => {
if (languageDialog != null) {
closeLanguageDialog();
} else {
closeSettings();
}
});
2021-08-24 21:00:56 +00:00
2021-08-18 20:08:14 +00:00
const onZoomSelectChange = useCallback(
(value: string) => {
const number = parseFloat(value);
2021-11-11 22:43:05 +00:00
onZoomFactorChange(number as unknown as ZoomFactorType);
2021-08-18 20:08:14 +00:00
},
[onZoomFactorChange]
);
const onAudioInputSelectChange = useCallback(
(value: string) => {
if (value === 'undefined') {
onSelectedMicrophoneChange(undefined);
} else {
onSelectedMicrophoneChange(availableMicrophones[parseInt(value, 10)]);
}
},
[onSelectedMicrophoneChange, availableMicrophones]
);
const selectors = useMemo(() => focusableSelectors.join(','), []);
const settingsPaneRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
const settingsPane = settingsPaneRef.current;
if (!settingsPane) {
return;
}
const elements = settingsPane.querySelectorAll<
| HTMLAnchorElement
| HTMLButtonElement
| HTMLInputElement
| HTMLSelectElement
| HTMLTextAreaElement
>(selectors);
if (!elements.length) {
return;
}
elements[0]?.focus();
}, [page, selectors]);
2021-08-18 20:08:14 +00:00
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]
);
2023-11-06 21:19:23 +00:00
const localeSearchOptions = useMemo(() => {
2023-11-14 21:08:28 +00:00
const collator = new Intl.Collator('en', { usage: 'sort' });
2023-11-06 21:19:23 +00:00
const availableLocalesOptions = availableLocales
.map(locale => {
const currentLocaleLabel = getLocaleDisplayName(resolvedLocale, locale);
const matchingLocaleLabel = getLocaleDisplayName(locale, locale);
2023-11-06 21:19:23 +00:00
return { locale, currentLocaleLabel, matchingLocaleLabel };
})
.sort((a, b) => {
2023-11-14 21:08:28 +00:00
return collator.compare(a.locale, b.locale);
2023-11-06 21:19:23 +00:00
});
const [localeOverrideMatches, localeOverrideNonMatches] = partition(
availableLocalesOptions,
option => {
return option.locale === localeOverride;
}
);
const preferredSystemLocaleMatch = LocaleMatcher.match(
preferredSystemLocales as Array<string>, // bad types
availableLocales as Array<string>, // bad types
'en',
{ algorithm: 'best fit' }
);
return [
...localeOverrideMatches,
{
locale: null,
currentLocaleLabel: i18n('icu:Preferences__Language__SystemLanguage'),
matchingLocaleLabel: getLocaleDisplayName(
2023-11-06 21:19:23 +00:00
preferredSystemLocaleMatch,
preferredSystemLocaleMatch
),
},
...localeOverrideNonMatches,
];
}, [
i18n,
availableLocales,
resolvedLocale,
localeOverride,
preferredSystemLocales,
getLocaleDisplayName,
2023-11-06 21:19:23 +00:00
]);
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]);
2021-08-18 20:08:14 +00:00
let settings: JSX.Element | undefined;
if (page === Page.General) {
settings = (
<>
<div className="Preferences__title">
<div className="Preferences__title--header">
2023-03-30 00:03:25 +00:00
{i18n('icu:Preferences__button--general')}
2021-08-18 20:08:14 +00:00
</div>
</div>
<SettingsRow>
2023-03-30 00:03:25 +00:00
<Control
left={i18n('icu:Preferences--device-name')}
right={deviceName}
/>
2021-08-18 20:08:14 +00:00
</SettingsRow>
2023-03-30 00:03:25 +00:00
<SettingsRow title={i18n('icu:Preferences--system')}>
2021-08-18 20:08:14 +00:00
{isAutoLaunchSupported && (
<Checkbox
checked={hasAutoLaunch}
2023-03-30 00:03:25 +00:00
label={i18n('icu:autoLaunchDescription')}
2021-08-18 20:08:14 +00:00
moduleClassName="Preferences__checkbox"
name="autoLaunch"
onChange={onAutoLaunchChange}
/>
)}
{isHideMenuBarSupported && (
<Checkbox
checked={hasHideMenuBar}
2023-03-30 00:03:25 +00:00
label={i18n('icu:hideMenuBar')}
2021-08-18 20:08:14 +00:00
moduleClassName="Preferences__checkbox"
name="hideMenuBar"
onChange={onHideMenuBarChange}
/>
)}
{isSystemTraySupported && (
<>
<Checkbox
checked={hasMinimizeToSystemTray}
2023-03-30 00:03:25 +00:00
label={i18n('icu:SystemTraySetting__minimize-to-system-tray')}
2021-08-18 20:08:14 +00:00
moduleClassName="Preferences__checkbox"
name="system-tray-setting-minimize-to-system-tray"
onChange={onMinimizeToSystemTrayChange}
/>
2022-09-06 22:09:52 +00:00
{isMinimizeToAndStartInSystemTraySupported && (
<Checkbox
checked={hasMinimizeToAndStartInSystemTray}
disabled={!hasMinimizeToSystemTray}
label={i18n(
2023-03-30 00:03:25 +00:00
'icu:SystemTraySetting__minimize-to-and-start-in-system-tray'
2022-09-06 22:09:52 +00:00
)}
moduleClassName="Preferences__checkbox"
name="system-tray-setting-minimize-to-and-start-in-system-tray"
onChange={onMinimizeToAndStartInSystemTrayChange}
/>
)}
2021-08-18 20:08:14 +00:00
</>
)}
</SettingsRow>
2023-03-30 00:03:25 +00:00
<SettingsRow title={i18n('icu:permissions')}>
2021-08-18 20:08:14 +00:00
<Checkbox
checked={hasMediaPermissions}
2023-03-30 00:03:25 +00:00
label={i18n('icu:mediaPermissionsDescription')}
2021-08-18 20:08:14 +00:00
moduleClassName="Preferences__checkbox"
name="mediaPermissions"
onChange={onMediaPermissionsChange}
/>
<Checkbox
checked={hasMediaCameraPermissions}
2023-03-30 00:03:25 +00:00
label={i18n('icu:mediaCameraPermissionsDescription')}
2021-08-18 20:08:14 +00:00
moduleClassName="Preferences__checkbox"
name="mediaCameraPermissions"
onChange={onMediaCameraPermissionsChange}
/>
</SettingsRow>
{isAutoDownloadUpdatesSupported && (
2023-03-30 00:03:25 +00:00
<SettingsRow title={i18n('icu:Preferences--updates')}>
<Checkbox
checked={hasAutoDownloadUpdate}
2023-03-30 00:03:25 +00:00
label={i18n('icu:Preferences__download-update')}
moduleClassName="Preferences__checkbox"
name="autoDownloadUpdate"
onChange={onAutoDownloadUpdateChange}
/>
</SettingsRow>
)}
2021-08-18 20:08:14 +00:00
</>
);
} else if (page === Page.Appearance) {
2021-09-02 23:29:16 +00:00
let zoomFactors = DEFAULT_ZOOM_FACTORS;
if (!zoomFactors.some(({ value }) => value === zoomFactor)) {
zoomFactors = [
...zoomFactors,
{
text: `${Math.round(zoomFactor * 100)}%`,
value: zoomFactor,
},
].sort((a, b) => a.value - b.value);
}
2021-08-18 20:08:14 +00:00
settings = (
<>
<div className="Preferences__title">
<div className="Preferences__title--header">
2023-03-30 00:03:25 +00:00
{i18n('icu:Preferences__button--appearance')}
2021-08-18 20:08:14 +00:00
</div>
</div>
<SettingsRow>
<Control
2023-11-06 21:19:23 +00:00
icon="Preferences__LanguageIcon"
left={i18n('icu:Preferences__Language__Label')}
right={
<span
className="Preferences__LanguageButton"
lang={localeOverride ?? resolvedLocale}
>
{localeOverride != null
? getLocaleDisplayName(resolvedLocale, localeOverride)
2023-11-06 21:19:23 +00:00
: i18n('icu:Preferences__Language__SystemLanguage')}
</span>
}
onClick={() => {
setLanguageDialog(LanguageDialog.Selection);
}}
/>
{languageDialog === LanguageDialog.Selection && (
<Modal
i18n={i18n}
modalName="Preferences__LanguageModal"
moduleClassName="Preferences__LanguageModal"
padded={false}
onClose={closeLanguageDialog}
title={i18n('icu:Preferences__Language__ModalTitle')}
modalHeaderChildren={
<SearchInput
i18n={i18n}
value={languageSearchInput}
placeholder={i18n(
'icu:Preferences__Language__SearchLanguages'
)}
moduleClassName="Preferences__LanguageModal__SearchInput"
onChange={event => {
setLanguageSearchInput(event.currentTarget.value);
}}
/>
}
modalFooter={
<>
<Button
variant={ButtonVariant.Secondary}
onClick={closeLanguageDialog}
>
{i18n('icu:cancel')}
</Button>
<Button
variant={ButtonVariant.Primary}
disabled={selectedLanguageLocale === localeOverride}
onClick={() => {
setLanguageDialog(LanguageDialog.Confirmation);
}}
>
{i18n('icu:Preferences__LanguageModal__Set')}
</Button>
</>
}
>
{localeSearchResults.length === 0 && (
<div className="Preferences__LanguageModal__NoResults">
{i18n('icu:Preferences__Language__NoResults', {
searchTerm: languageSearchInput.trim(),
})}
</div>
)}
{localeSearchResults.map(option => {
const id = `${languageId}:${option.locale ?? 'system'}`;
const isSelected = option.locale === selectedLanguageLocale;
return (
<button
key={id}
type="button"
className="Preferences__LanguageModal__Item"
onClick={() => {
setSelectedLanguageLocale(option.locale);
}}
aria-pressed={isSelected}
>
<span className="Preferences__LanguageModal__Item__Inner">
<span className="Preferences__LanguageModal__Item__Label">
<span className="Preferences__LanguageModal__Item__Current">
{option.currentLocaleLabel}
</span>
{option.matchingLocaleLabel != null && (
<span
lang={option.locale ?? resolvedLocale}
className="Preferences__LanguageModal__Item__Matching"
>
{option.matchingLocaleLabel}
</span>
)}
</span>
{isSelected && (
<span className="Preferences__LanguageModal__Item__Check" />
)}
</span>
</button>
);
})}
</Modal>
)}
{languageDialog === LanguageDialog.Confirmation && (
<ConfirmationDialog
dialogName="Preferences__Language"
i18n={i18n}
title={i18n('icu:Preferences__LanguageModal__Restart__Title')}
onCancel={closeLanguageDialog}
onClose={closeLanguageDialog}
cancelText={i18n('icu:cancel')}
actions={[
{
text: i18n('icu:Preferences__LanguageModal__Restart__Button'),
style: 'affirmative',
action: () => {
onLocaleChange(selectedLanguageLocale);
},
},
]}
>
{i18n('icu:Preferences__LanguageModal__Restart__Description')}
</ConfirmationDialog>
)}
<Control
icon
left={
<label htmlFor={themeSelectId}>
2023-03-30 00:03:25 +00:00
{i18n('icu:Preferences--theme')}
</label>
}
2021-08-18 20:08:14 +00:00
right={
<Select
id={themeSelectId}
2021-08-18 20:08:14 +00:00
onChange={onThemeChange}
options={[
{
2023-03-30 00:03:25 +00:00
text: i18n('icu:themeSystem'),
2021-08-18 20:08:14 +00:00
value: 'system',
},
{
2023-03-30 00:03:25 +00:00
text: i18n('icu:themeLight'),
2021-08-18 20:08:14 +00:00
value: 'light',
},
{
2023-03-30 00:03:25 +00:00
text: i18n('icu:themeDark'),
2021-08-18 20:08:14 +00:00
value: 'dark',
},
]}
value={themeSetting}
/>
}
/>
<Control
2023-11-06 21:19:23 +00:00
icon
2023-03-30 00:03:25 +00:00
left={i18n('icu:showChatColorEditor')}
2021-08-18 20:08:14 +00:00
onClick={() => {
setPage(Page.ChatColor);
}}
right={
<div
className={`ConversationDetails__chat-color ConversationDetails__chat-color--${defaultConversationColor.color}`}
2021-08-18 20:08:14 +00:00
style={{
...getCustomColorStyle(
defaultConversationColor.customColorData?.value
),
}}
/>
}
/>
<Control
2023-11-06 21:19:23 +00:00
icon
left={
2023-03-30 00:03:25 +00:00
<label htmlFor={zoomSelectId}>
{i18n('icu:Preferences--zoom')}
</label>
}
2021-08-18 20:08:14 +00:00
right={
<Select
id={zoomSelectId}
2021-08-18 20:08:14 +00:00
onChange={onZoomSelectChange}
2021-09-02 23:29:16 +00:00
options={zoomFactors}
2021-08-18 20:08:14 +00:00
value={zoomFactor}
/>
}
/>
</SettingsRow>
</>
);
} else if (page === Page.Chats) {
let spellCheckDirtyText: string | undefined;
if (initialSpellCheckSetting !== hasSpellCheck) {
spellCheckDirtyText = hasSpellCheck
2023-03-30 00:03:25 +00:00
? i18n('icu:spellCheckWillBeEnabled')
: i18n('icu:spellCheckWillBeDisabled');
2021-08-18 20:08:14 +00:00
}
const lastSyncDate = new Date(lastSyncTime || 0);
settings = (
<>
<div className="Preferences__title">
<div className="Preferences__title--header">
2023-03-30 00:03:25 +00:00
{i18n('icu:Preferences__button--chats')}
2021-08-18 20:08:14 +00:00
</div>
</div>
2023-03-30 00:03:25 +00:00
<SettingsRow title={i18n('icu:Preferences__button--chats')}>
2021-08-18 20:08:14 +00:00
<Checkbox
checked={hasSpellCheck}
description={spellCheckDirtyText}
2023-03-30 00:03:25 +00:00
label={i18n('icu:spellCheckDescription')}
2021-08-18 20:08:14 +00:00
moduleClassName="Preferences__checkbox"
name="spellcheck"
onChange={onSpellCheckChange}
/>
2023-12-07 23:59:54 +00:00
<Checkbox
checked={hasTextFormatting}
label={i18n('icu:textFormattingDescription')}
moduleClassName="Preferences__checkbox"
name="textFormatting"
onChange={onTextFormattingChange}
/>
2021-08-18 20:08:14 +00:00
<Checkbox
checked={hasLinkPreviews}
2023-03-30 00:03:25 +00:00
description={i18n('icu:Preferences__link-previews--description')}
2021-08-18 20:08:14 +00:00
disabled
2023-03-30 00:03:25 +00:00
label={i18n('icu:Preferences__link-previews--title')}
2021-08-18 20:08:14 +00:00
moduleClassName="Preferences__checkbox"
name="linkPreviews"
onChange={noop}
/>
2023-12-18 23:22:46 +00:00
<Checkbox
checked={hasAutoConvertEmoji}
description={i18n(
'icu:Preferences__auto-convert-emoji--description'
)}
label={i18n('icu:Preferences__auto-convert-emoji--title')}
moduleClassName="Preferences__checkbox"
name="autoConvertEmoji"
onChange={onAutoConvertEmojiChange}
/>
<Control
2023-03-30 00:03:25 +00:00
left={i18n('icu:Preferences__sent-media-quality')}
right={
<Select
onChange={onSentMediaQualityChange}
options={[
{
2023-03-30 00:03:25 +00:00
text: i18n('icu:sentMediaQualityStandard'),
value: 'standard',
},
{
2023-03-30 00:03:25 +00:00
text: i18n('icu:sentMediaQualityHigh'),
value: 'high',
},
]}
value={sentMediaQualitySetting}
/>
}
/>
2021-08-18 20:08:14 +00:00
</SettingsRow>
{isSyncSupported && (
<SettingsRow>
<Control
left={
<>
2023-03-30 00:03:25 +00:00
<div>{i18n('icu:sync')}</div>
2021-08-18 20:08:14 +00:00
<div className="Preferences__description">
2023-03-30 00:03:25 +00:00
{i18n('icu:syncExplanation')}{' '}
{i18n('icu:Preferences--lastSynced', {
2021-08-18 20:08:14 +00:00
date: lastSyncDate.toLocaleDateString(),
time: lastSyncDate.toLocaleTimeString(),
})}
</div>
{showSyncFailed && (
<div className="Preferences__description Preferences__description--error">
2023-03-30 00:03:25 +00:00
{i18n('icu:syncFailed')}
2021-08-18 20:08:14 +00:00
</div>
)}
</>
}
right={
<div className="Preferences__right-button">
<Button
2023-06-15 18:34:50 +00:00
aria-label={
nowSyncing ? i18n('icu:syncing') : i18n('icu:syncNow')
}
aria-live="polite"
2021-08-18 20:08:14 +00:00
disabled={nowSyncing}
onClick={async () => {
setShowSyncFailed(false);
setNowSyncing(true);
try {
await makeSyncRequest();
onLastSyncTimeChange(Date.now());
} catch (err) {
setShowSyncFailed(true);
} finally {
setNowSyncing(false);
}
}}
variant={ButtonVariant.SecondaryAffirmative}
>
2023-03-30 00:03:25 +00:00
{nowSyncing ? (
<Spinner svgSize="small" />
) : (
i18n('icu:syncNow')
)}
2021-08-18 20:08:14 +00:00
</Button>
</div>
}
/>
</SettingsRow>
)}
</>
);
} else if (page === Page.Calls) {
settings = (
<>
<div className="Preferences__title">
<div className="Preferences__title--header">
2023-03-30 00:03:25 +00:00
{i18n('icu:Preferences__button--calls')}
2021-08-18 20:08:14 +00:00
</div>
</div>
2023-03-30 00:03:25 +00:00
<SettingsRow title={i18n('icu:calling')}>
2021-08-18 20:08:14 +00:00
<Checkbox
checked={hasIncomingCallNotifications}
2023-03-30 00:03:25 +00:00
label={i18n('icu:incomingCallNotificationDescription')}
2021-08-18 20:08:14 +00:00
moduleClassName="Preferences__checkbox"
name="incomingCallNotification"
onChange={onIncomingCallNotificationsChange}
/>
<Checkbox
checked={hasCallRingtoneNotification}
2023-03-30 00:03:25 +00:00
label={i18n('icu:callRingtoneNotificationDescription')}
2021-08-18 20:08:14 +00:00
moduleClassName="Preferences__checkbox"
name="callRingtoneNotification"
onChange={onCallRingtoneNotificationChange}
/>
</SettingsRow>
2023-03-30 00:03:25 +00:00
<SettingsRow title={i18n('icu:Preferences__devices')}>
2021-08-18 20:08:14 +00:00
<Control
left={
<>
<label className="Preferences__select-title" htmlFor="video">
2023-03-30 00:03:25 +00:00
{i18n('icu:callingDeviceSelection__label--video')}
2021-08-18 20:08:14 +00:00
</label>
<Select
2023-03-30 00:03:25 +00:00
ariaLabel={i18n('icu:callingDeviceSelection__label--video')}
2021-08-18 20:08:14 +00:00
disabled={!availableCameras.length}
moduleClassName="Preferences__select"
name="video"
onChange={onSelectedCameraChange}
options={
availableCameras.length
? availableCameras.map(device => ({
text: localizeDefault(i18n, device.label),
value: device.deviceId,
}))
: [
{
text: i18n(
2023-03-30 00:03:25 +00:00
'icu:callingDeviceSelection__select--no-device'
2021-08-18 20:08:14 +00:00
),
value: 'undefined',
},
]
}
value={selectedCamera}
/>
</>
}
right={<div />}
/>
<Control
left={
<>
<label
className="Preferences__select-title"
htmlFor="audio-input"
>
2023-03-30 00:03:25 +00:00
{i18n('icu:callingDeviceSelection__label--audio-input')}
2021-08-18 20:08:14 +00:00
</label>
<Select
2023-03-30 00:03:25 +00:00
ariaLabel={i18n(
'icu:callingDeviceSelection__label--audio-input'
)}
2021-08-18 20:08:14 +00:00
disabled={!availableMicrophones.length}
moduleClassName="Preferences__select"
name="audio-input"
onChange={onAudioInputSelectChange}
options={
availableMicrophones.length
? availableMicrophones.map(device => ({
text: localizeDefault(i18n, device.name),
value: device.index,
}))
: [
{
text: i18n(
2023-03-30 00:03:25 +00:00
'icu:callingDeviceSelection__select--no-device'
2021-08-18 20:08:14 +00:00
),
value: 'undefined',
},
]
}
value={selectedMicrophone?.index}
/>
</>
}
right={<div />}
/>
<Control
left={
<>
<label
className="Preferences__select-title"
htmlFor="audio-output"
>
2023-03-30 00:03:25 +00:00
{i18n('icu:callingDeviceSelection__label--audio-output')}
2021-08-18 20:08:14 +00:00
</label>
<Select
ariaLabel={i18n(
2023-03-30 00:03:25 +00:00
'icu:callingDeviceSelection__label--audio-output'
)}
2021-08-18 20:08:14 +00:00
disabled={!availableSpeakers.length}
moduleClassName="Preferences__select"
name="audio-output"
onChange={onAudioOutputSelectChange}
options={
availableSpeakers.length
? availableSpeakers.map(device => ({
text: localizeDefault(i18n, device.name),
value: device.index,
}))
: [
{
text: i18n(
2023-03-30 00:03:25 +00:00
'icu:callingDeviceSelection__select--no-device'
2021-08-18 20:08:14 +00:00
),
value: 'undefined',
},
]
}
value={selectedSpeaker?.index}
/>
</>
}
right={<div />}
/>
</SettingsRow>
2023-03-30 00:03:25 +00:00
<SettingsRow title={i18n('icu:Preferences--advanced')}>
2021-08-18 20:08:14 +00:00
<Checkbox
checked={hasRelayCalls}
2023-03-30 00:03:25 +00:00
description={i18n('icu:alwaysRelayCallsDetail')}
label={i18n('icu:alwaysRelayCallsDescription')}
2021-08-18 20:08:14 +00:00
moduleClassName="Preferences__checkbox"
name="relayCalls"
onChange={onRelayCallsChange}
/>
</SettingsRow>
</>
);
} else if (page === Page.Notifications) {
settings = (
<>
<div className="Preferences__title">
<div className="Preferences__title--header">
2023-03-30 00:03:25 +00:00
{i18n('icu:Preferences__button--notifications')}
2021-08-18 20:08:14 +00:00
</div>
</div>
<SettingsRow>
<Checkbox
checked={hasNotifications}
2023-03-30 00:03:25 +00:00
label={i18n('icu:Preferences__enable-notifications')}
2021-08-18 20:08:14 +00:00
moduleClassName="Preferences__checkbox"
name="notifications"
onChange={onNotificationsChange}
/>
<Checkbox
checked={hasCallNotifications}
2023-03-30 00:03:25 +00:00
label={i18n('icu:callSystemNotificationDescription')}
2021-08-18 20:08:14 +00:00
moduleClassName="Preferences__checkbox"
name="callSystemNotification"
onChange={onCallNotificationsChange}
/>
{isNotificationAttentionSupported && (
<Checkbox
checked={hasNotificationAttention}
2023-03-30 00:03:25 +00:00
label={i18n('icu:notificationDrawAttention')}
2021-08-18 20:08:14 +00:00
moduleClassName="Preferences__checkbox"
name="notificationDrawAttention"
onChange={onNotificationAttentionChange}
/>
)}
<Checkbox
checked={hasCountMutedConversations}
2023-03-30 00:03:25 +00:00
label={i18n('icu:countMutedConversationsDescription')}
2021-08-18 20:08:14 +00:00
moduleClassName="Preferences__checkbox"
name="countMutedConversations"
onChange={onCountMutedConversationsChange}
/>
</SettingsRow>
<SettingsRow>
<Control
2023-03-30 00:03:25 +00:00
left={i18n('icu:Preferences--notification-content')}
2021-08-18 20:08:14 +00:00
right={
<Select
2023-03-30 00:03:25 +00:00
ariaLabel={i18n('icu:Preferences--notification-content')}
2021-08-18 20:08:14 +00:00
disabled={!hasNotifications}
onChange={onNotificationContentChange}
options={[
{
2023-03-30 00:03:25 +00:00
text: i18n('icu:nameAndMessage'),
2021-08-18 20:08:14 +00:00
value: 'message',
},
{
2023-03-30 00:03:25 +00:00
text: i18n('icu:nameOnly'),
2021-08-18 20:08:14 +00:00
value: 'name',
},
{
2023-03-30 00:03:25 +00:00
text: i18n('icu:noNameOrMessage'),
2021-08-18 20:08:14 +00:00
value: 'count',
},
]}
value={notificationContent}
/>
}
/>
</SettingsRow>
<SettingsRow>
<Checkbox
checked={hasAudioNotifications}
label={i18n('icu:audioNotificationDescription')}
moduleClassName="Preferences__checkbox"
name="audioNotification"
onChange={onAudioNotificationsChange}
/>
<Checkbox
checked={hasMessageAudio}
description={i18n('icu:Preferences__message-audio-description')}
label={i18n('icu:Preferences__message-audio-title')}
moduleClassName="Preferences__checkbox"
name="messageAudio"
onChange={onMessageAudioChange}
/>
</SettingsRow>
2021-08-18 20:08:14 +00:00
</>
);
} else if (page === Page.Privacy) {
2021-11-11 22:43:05 +00:00
const isCustomDisappearingMessageValue =
!DEFAULT_DURATIONS_SET.has(universalExpireTimer);
2021-08-18 20:08:14 +00:00
settings = (
<>
<div className="Preferences__title">
<div className="Preferences__title--header">
2023-03-30 00:03:25 +00:00
{i18n('icu:Preferences__button--privacy')}
2021-08-18 20:08:14 +00:00
</div>
</div>
2023-02-23 21:32:19 +00:00
{isPhoneNumberSharingSupported ? (
<button
type="button"
className="Preferences__link"
onClick={() => setPage(Page.PNP)}
>
<h3 className="Preferences__padding">
{i18n('icu:Preferences__pnp__row--title')}
</h3>
<div className="Preferences__padding Preferences__description">
{i18n('icu:Preferences__pnp__row--body')}
</div>
</button>
) : null}
2021-08-18 20:08:14 +00:00
<SettingsRow>
<Control
2023-03-30 00:03:25 +00:00
left={i18n('icu:Preferences--blocked')}
2023-04-03 19:03:00 +00:00
right={i18n('icu:Preferences--blocked-count', {
num: blockedCount,
})}
2021-08-18 20:08:14 +00:00
/>
</SettingsRow>
2023-03-30 00:03:25 +00:00
<SettingsRow title={i18n('icu:Preferences--messaging')}>
2021-08-18 20:08:14 +00:00
<Checkbox
checked={hasReadReceipts}
disabled
2023-03-30 00:03:25 +00:00
label={i18n('icu:Preferences--read-receipts')}
2021-08-18 20:08:14 +00:00
moduleClassName="Preferences__checkbox"
name="readReceipts"
onChange={noop}
/>
<Checkbox
checked={hasTypingIndicators}
disabled
2023-03-30 00:03:25 +00:00
label={i18n('icu:Preferences--typing-indicators')}
2021-08-18 20:08:14 +00:00
moduleClassName="Preferences__checkbox"
name="typingIndicators"
onChange={noop}
/>
<div className="Preferences__padding">
<div className="Preferences__description">
2023-03-30 00:03:25 +00:00
{i18n('icu:Preferences__privacy--description')}
2021-08-18 20:08:14 +00:00
</div>
</div>
</SettingsRow>
{showDisappearingTimerDialog && (
<DisappearingTimeDialog
i18n={i18n}
initialValue={universalExpireTimer}
onClose={() => setShowDisappearingTimerDialog(false)}
onSubmit={onUniversalExpireTimerChange}
/>
)}
<SettingsRow title={i18n('icu:disappearingMessages')}>
2021-08-18 20:08:14 +00:00
<Control
left={
<>
<div>
2023-03-30 00:03:25 +00:00
{i18n('icu:settings__DisappearingMessages__timer__label')}
2021-08-18 20:08:14 +00:00
</div>
<div className="Preferences__description">
2023-03-30 00:03:25 +00:00
{i18n('icu:settings__DisappearingMessages__footer')}
2021-08-18 20:08:14 +00:00
</div>
</>
}
right={
<Select
2023-03-30 00:03:25 +00:00
ariaLabel={i18n(
'icu:settings__DisappearingMessages__timer__label'
)}
2021-08-18 20:08:14 +00:00
onChange={value => {
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
2022-11-16 20:18:02 +00:00
: DurationInSeconds.fromSeconds(-1),
2021-08-18 20:08:14 +00:00
text: isCustomDisappearingMessageValue
? formatExpirationTimer(i18n, universalExpireTimer)
2023-03-30 00:03:25 +00:00
: i18n('icu:selectedCustomDisappearingTimeOption'),
2021-08-18 20:08:14 +00:00
},
])}
value={universalExpireTimer}
/>
}
/>
</SettingsRow>
2023-12-07 23:59:54 +00:00
<SettingsRow title={i18n('icu:Stories__title')}>
<Control
left={
<label htmlFor={storiesId}>
<div>{i18n('icu:Stories__settings-toggle--title')}</div>
<div className="Preferences__description">
{i18n('icu:Stories__settings-toggle--description')}
</div>
</label>
}
right={
hasStoriesDisabled ? (
<Button
onClick={() => onHasStoriesDisabledChanged(false)}
variant={ButtonVariant.Secondary}
>
{i18n('icu:Preferences__turn-stories-on')}
</Button>
) : (
<Button
className="Preferences__stories-off"
onClick={() => setConfirmStoriesOff(true)}
variant={ButtonVariant.SecondaryDestructive}
>
{i18n('icu:Preferences__turn-stories-off')}
</Button>
)
}
/>
</SettingsRow>
2021-08-18 20:08:14 +00:00
<SettingsRow>
<Control
left={
<>
2023-03-30 00:03:25 +00:00
<div>{i18n('icu:clearDataHeader')}</div>
2021-08-18 20:08:14 +00:00
<div className="Preferences__description">
2023-03-30 00:03:25 +00:00
{i18n('icu:clearDataExplanation')}
2021-08-18 20:08:14 +00:00
</div>
</>
}
right={
<div className="Preferences__right-button">
<Button
2021-08-24 20:57:34 +00:00
onClick={() => setConfirmDelete(true)}
2021-08-18 20:08:14 +00:00
variant={ButtonVariant.SecondaryDestructive}
>
2023-03-30 00:03:25 +00:00
{i18n('icu:clearDataButton')}
2021-08-18 20:08:14 +00:00
</Button>
</div>
}
/>
</SettingsRow>
2021-08-24 20:57:34 +00:00
{confirmDelete ? (
<ConfirmationDialog
2022-09-27 20:24:21 +00:00
dialogName="Preference.deleteAllData"
2021-08-24 20:57:34 +00:00
actions={[
{
action: doDeleteAllData,
style: 'negative',
2023-03-30 00:03:25 +00:00
text: i18n('icu:clearDataButton'),
2021-08-24 20:57:34 +00:00
},
]}
i18n={i18n}
onClose={() => {
setConfirmDelete(false);
}}
2023-03-30 00:03:25 +00:00
title={i18n('icu:deleteAllDataHeader')}
2021-08-24 20:57:34 +00:00
>
2023-03-30 00:03:25 +00:00
{i18n('icu:deleteAllDataBody')}
2021-08-24 20:57:34 +00:00
</ConfirmationDialog>
) : null}
2022-10-03 23:56:10 +00:00
{confirmStoriesOff ? (
<ConfirmationDialog
dialogName="Preference.turnStoriesOff"
actions={[
{
action: () => onHasStoriesDisabledChanged(true),
2022-10-03 23:56:10 +00:00
style: 'negative',
2023-03-30 00:03:25 +00:00
text: i18n('icu:Preferences__turn-stories-off--action'),
2022-10-03 23:56:10 +00:00
},
]}
i18n={i18n}
onClose={() => {
setConfirmStoriesOff(false);
}}
>
2023-03-30 00:03:25 +00:00
{i18n('icu:Preferences__turn-stories-off--body')}
2022-10-03 23:56:10 +00:00
</ConfirmationDialog>
) : null}
2021-08-18 20:08:14 +00:00
</>
);
} else if (page === Page.ChatColor) {
settings = (
<>
<div className="Preferences__title">
<button
2023-03-30 00:03:25 +00:00
aria-label={i18n('icu:goBack')}
2021-08-18 20:08:14 +00:00
className="Preferences__back-icon"
onClick={() => setPage(Page.Appearance)}
type="button"
/>
<div className="Preferences__title--header">
2023-03-30 00:03:25 +00:00
{i18n('icu:ChatColorPicker__menu-title')}
2021-08-18 20:08:14 +00:00
</div>
</div>
<ChatColorPicker
customColors={customColors}
getConversationsWithCustomColor={getConversationsWithCustomColor}
i18n={i18n}
isGlobal
selectedColor={defaultConversationColor.color}
selectedCustomColor={defaultConversationColor.customColorData || {}}
// actions
addCustomColor={addCustomColor}
colorSelected={noop}
editCustomColor={editCustomColor}
removeCustomColor={removeCustomColor}
removeCustomColorOnConversations={removeCustomColorOnConversations}
resetAllChatColors={resetAllChatColors}
resetDefaultChatColor={resetDefaultChatColor}
setGlobalDefaultConversationColor={setGlobalDefaultConversationColor}
/>
</>
);
2023-02-23 21:32:19 +00:00
} else if (page === Page.PNP) {
settings = (
<>
<div className="Preferences__title">
<button
2023-03-30 00:03:25 +00:00
aria-label={i18n('icu:goBack')}
2023-02-23 21:32:19 +00:00
className="Preferences__back-icon"
onClick={() => setPage(Page.Privacy)}
type="button"
/>
<div className="Preferences__title--header">
{i18n('icu:Preferences__pnp--page-title')}
</div>
</div>
2023-03-09 00:35:25 +00:00
<SettingsRow
title={i18n('icu:Preferences__pnp__sharing--title')}
className={classNames('Preferences__settings-row--pnp-sharing', {
'Preferences__settings-row--pnp-sharing--nobody':
whoCanSeeMe === PhoneNumberSharingMode.Nobody,
})}
>
2023-02-23 21:32:19 +00:00
<SettingsRadio
onChange={onWhoCanSeeMeChange}
options={[
{
text: i18n('icu:Preferences__pnp__sharing__everyone'),
value: PhoneNumberSharingMode.Everybody,
},
{
text: i18n('icu:Preferences__pnp__sharing__nobody'),
value: PhoneNumberSharingMode.Nobody,
},
]}
value={whoCanSeeMe}
/>
<div className="Preferences__padding">
<div className="Preferences__description">
{whoCanSeeMe === PhoneNumberSharingMode.Everybody
? i18n('icu:Preferences__pnp__sharing--description--everyone')
: i18n('icu:Preferences__pnp__sharing--description--nobody')}
</div>
</div>
</SettingsRow>
<SettingsRow
title={i18n('icu:Preferences__pnp__discoverability--title')}
>
<SettingsRadio
onChange={onWhoCanFindMeChange}
options={[
{
text: i18n('icu:Preferences__pnp__discoverability__everyone'),
value: PhoneNumberDiscoverability.Discoverable,
},
...(whoCanSeeMe === PhoneNumberSharingMode.Nobody
? [
{
text: i18n(
'icu:Preferences__pnp__discoverability__nobody'
),
value: PhoneNumberDiscoverability.NotDiscoverable,
},
]
: []),
]}
value={whoCanFindMe}
/>
<div className="Preferences__padding">
<div className="Preferences__description">
{whoCanFindMe === PhoneNumberDiscoverability.Discoverable
? i18n(
'icu:Preferences__pnp__discoverability--description--everyone'
)
: i18n(
'icu:Preferences__pnp__discoverability--description--nobody'
)}
</div>
</div>
</SettingsRow>
</>
);
2021-08-18 20:08:14 +00:00
}
return (
<TitleBarContainer
2022-07-05 16:44:53 +00:00
hasCustomTitleBar={hasCustomTitleBar}
theme={theme}
executeMenuRole={executeMenuRole}
>
2023-02-23 21:32:19 +00:00
<div className="module-title-bar-drag-area" />
<div className="Preferences">
<div className="Preferences__page-selector">
<button
type="button"
className={classNames({
Preferences__button: true,
'Preferences__button--general': true,
'Preferences__button--selected': page === Page.General,
})}
onClick={() => setPage(Page.General)}
>
2023-03-30 00:03:25 +00:00
{i18n('icu:Preferences__button--general')}
</button>
<button
type="button"
className={classNames({
Preferences__button: true,
'Preferences__button--appearance': true,
'Preferences__button--selected':
page === Page.Appearance || page === Page.ChatColor,
})}
onClick={() => setPage(Page.Appearance)}
>
2023-03-30 00:03:25 +00:00
{i18n('icu:Preferences__button--appearance')}
</button>
<button
type="button"
className={classNames({
Preferences__button: true,
'Preferences__button--chats': true,
'Preferences__button--selected': page === Page.Chats,
})}
onClick={() => setPage(Page.Chats)}
>
2023-03-30 00:03:25 +00:00
{i18n('icu:Preferences__button--chats')}
</button>
<button
type="button"
className={classNames({
Preferences__button: true,
'Preferences__button--calls': true,
'Preferences__button--selected': page === Page.Calls,
})}
onClick={() => setPage(Page.Calls)}
>
2023-03-30 00:03:25 +00:00
{i18n('icu:Preferences__button--calls')}
</button>
<button
type="button"
className={classNames({
Preferences__button: true,
'Preferences__button--notifications': true,
'Preferences__button--selected': page === Page.Notifications,
})}
onClick={() => setPage(Page.Notifications)}
>
2023-03-30 00:03:25 +00:00
{i18n('icu:Preferences__button--notifications')}
</button>
2023-11-06 21:19:23 +00:00
<button
type="button"
className={classNames({
Preferences__button: true,
'Preferences__button--privacy': true,
2023-02-23 21:32:19 +00:00
'Preferences__button--selected':
page === Page.Privacy || page === Page.PNP,
})}
onClick={() => setPage(Page.Privacy)}
>
2023-03-30 00:03:25 +00:00
{i18n('icu:Preferences__button--privacy')}
</button>
</div>
<div className="Preferences__settings-pane" ref={settingsPaneRef}>
{settings}
</div>
2021-08-18 20:08:14 +00:00
</div>
</TitleBarContainer>
2021-08-18 20:08:14 +00:00
);
2022-11-18 00:45:19 +00:00
}
2021-08-18 20:08:14 +00:00
2022-11-18 00:45:19 +00:00
function SettingsRow({
2021-08-18 20:08:14 +00:00
children,
title,
2023-02-23 21:32:19 +00:00
className,
2021-08-18 20:08:14 +00:00
}: {
children: ReactNode;
title?: string;
2023-02-23 21:32:19 +00:00
className?: string;
2022-11-18 00:45:19 +00:00
}): JSX.Element {
2021-08-18 20:08:14 +00:00
return (
2023-05-03 20:26:27 +00:00
<fieldset className={classNames('Preferences__settings-row', className)}>
{title && <legend className="Preferences__padding">{title}</legend>}
2021-08-18 20:08:14 +00:00
{children}
2023-05-03 20:26:27 +00:00
</fieldset>
2021-08-18 20:08:14 +00:00
);
2022-11-18 00:45:19 +00:00
}
2021-08-18 20:08:14 +00:00
2022-11-18 00:45:19 +00:00
function Control({
2023-11-06 21:19:23 +00:00
icon,
2021-08-18 20:08:14 +00:00
left,
onClick,
right,
}: {
2023-11-06 21:19:23 +00:00
/** A className or `true` to leave room for icon */
icon?: string | true;
2021-08-18 20:08:14 +00:00
left: ReactNode;
onClick?: () => unknown;
right: ReactNode;
2022-11-18 00:45:19 +00:00
}): JSX.Element {
2021-08-18 20:08:14 +00:00
const content = (
<>
2023-11-06 21:19:23 +00:00
{icon && (
<div
className={classNames(
'Preferences__control--icon',
icon === true ? null : icon
)}
/>
)}
2021-08-18 20:08:14 +00:00
<div className="Preferences__control--key">{left}</div>
<div className="Preferences__control--value">{right}</div>
</>
);
if (onClick) {
return (
<button
className="Preferences__control Preferences__control--clickable"
type="button"
onClick={onClick}
>
{content}
</button>
);
}
return <div className="Preferences__control">{content}</div>;
2022-11-18 00:45:19 +00:00
}
2021-08-18 20:08:14 +00:00
2023-02-23 21:32:19 +00:00
type SettingsRadioOptionType<Enum> = Readonly<{
text: string;
value: Enum;
}>;
function SettingsRadio<Enum>({
value,
options,
onChange,
}: {
value: Enum;
options: ReadonlyArray<SettingsRadioOptionType<Enum>>;
onChange: (value: Enum) => void;
}): JSX.Element {
const htmlIds = useMemo(() => {
return Array.from({ length: options.length }, () => uuid());
}, [options.length]);
return (
<div className="Preferences__padding">
{options.map(({ text, value: optionValue }, i) => {
const htmlId = htmlIds[i];
return (
<label
className="Preferences__settings-radio__label"
key={htmlId}
htmlFor={htmlId}
>
<CircleCheckbox
isRadio
variant={CircleCheckboxVariant.Small}
id={htmlId}
checked={value === optionValue}
onChange={() => onChange(optionValue)}
/>
{text}
</label>
);
})}
</div>
);
}
2021-08-18 20:08:14 +00:00
function localizeDefault(i18n: LocalizerType, deviceLabel: string): string {
return deviceLabel.toLowerCase().startsWith('default')
? deviceLabel.replace(
/default/i,
2023-03-30 00:03:25 +00:00
i18n('icu:callingDeviceSelection__select--default')
2021-08-18 20:08:14 +00:00
)
: deviceLabel;
}