PNP Settings

This commit is contained in:
Fedor Indutny 2023-02-23 13:32:19 -08:00 committed by GitHub
parent 5bcf71ef2c
commit 5d110964b9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 562 additions and 149 deletions

View file

@ -5429,23 +5429,75 @@
},
"Preferences__who-can--title": {
"message": "Who can...",
"description": "Title for the 'who can do X' setting"
"description": "(deleted 2022/02/14) Title for the 'who can do X' setting"
},
"Preferences__privacy--description": {
"message": "To change these settings, open the Signal app on your mobile device and navigate to Settings > Privacy",
"description": "Description for the 'who can do X' setting"
"description": "(deleted 2022/02/14) Description for the 'who can do X' setting"
},
"Preferences__who-can--everybody": {
"message": "Everybody",
"description": "Option for who can see my X select"
"description": "(deleted 2022/02/14) Option for who can see my X select"
},
"Preferences__who-can--contacts": {
"message": "My Contacts",
"description": "Option for who can see my X select"
"description": "(deleted 2022/02/14) Option for who can see my X select"
},
"Preferences__who-can--nobody": {
"message": "Nobody",
"description": "Option for who can see my X select"
"description": "(deleted 2022/02/14) Option for who can see my X select"
},
"icu:Preferences__pnp__row--title": {
"messageformat": "Phone Number",
"description": "Title of Phone Number row in Privacy section of Preferences window"
},
"icu:Preferences__pnp__row--body": {
"messageformat": "Choose who can see your phone number and who can contact you on Signal with it.",
"description": "Body of Phone Number row in Privacy section of Preferences window"
},
"icu:Preferences__pnp__sharing--title": {
"messageformat": "Who can see my number",
"description": "Title for the phone number sharing setting row"
},
"icu:Preferences__pnp__sharing--description--everyone": {
"messageformat": "Your phone number will be visible to people and groups you message. People who have your number in their phone contacts will also see it on Signal.",
"description": "Description for the phone number sharing setting row when the value is Everyone"
},
"icu:Preferences__pnp__sharing--description--nobody": {
"messageformat": "Nobody will see your phone number on Signal.",
"description": "Description for the phone number sharing setting row when the value is Nobody"
},
"icu:Preferences__pnp--page-title": {
"messageformat": "Phone Number",
"description": "Title of the page in Phone Number Privacy settings"
},
"icu:Preferences__pnp__sharing__everyone": {
"messageformat": "Everyone",
"description": "Option for sharing phone number with everyone"
},
"icu:Preferences__pnp__sharing__nobody": {
"messageformat": "Nobody",
"description": "Option for sharing phone number with nobody"
},
"icu:Preferences__pnp__discoverability--title": {
"messageformat": "Who can find me by number",
"description": "Title for the phone number discoverability setting row"
},
"icu:Preferences__pnp__discoverability--description--everyone": {
"messageformat": "Anyone who has your phone number in their contacts will see you as a contact on Signal. Others will be able to reach you with your phone number when they start a new chat or group.",
"description": "Description for the phone number discoverability setting row wth the value is everyone"
},
"icu:Preferences__pnp__discoverability--description--nobody": {
"messageformat": "Nobody on Signal will be able to reach you with your phone number.",
"description": "Description for the phone number discoverability setting row wth the value is nobody"
},
"icu:Preferences__pnp__discoverability__everyone": {
"messageformat": "Everyone",
"description": "Option for letting everyone discover you by phone number"
},
"icu:Preferences__pnp__discoverability__nobody": {
"messageformat": "Nobody",
"description": "Option for letting nobody discover you by phone number"
},
"Preferences--messaging": {
"message": "Messaging",

View file

@ -29,7 +29,6 @@
</head>
<body>
<div id="app"></div>
<script type="text/javascript" src="ts/set_os_class.js"></script>
<script type="application/javascript" src="ts/windows/init.js"></script>
</body>
</html>

View file

@ -1268,7 +1268,7 @@ async function showSettingsWindow() {
frame: true,
resizable: false,
title: getResolvedMessagesLocale().i18n('signalDesktopPreferences'),
titleBarStyle: nonMainTitleBarStyle,
titleBarStyle: mainTitleBarStyle,
titleBarOverlay,
autoHideMenuBar: true,
backgroundColor: await getBackgroundColor(),

View file

@ -33,7 +33,6 @@
type="application/javascript"
src="ts/windows/applyTheme.js"
></script>
<script type="text/javascript" src="ts/set_os_class.js"></script>
<script type="application/javascript" src="ts/windows/init.js"></script>
</body>
</html>

View file

@ -52,7 +52,7 @@
"danger:local": "./danger/danger.sh local --base main",
"danger:ci": "./danger/danger.sh ci --base origin/main",
"format": "pprettier --write '**/*.{ts,tsx,d.ts,js,json,html,scss,md,yml,yaml}' '!node_modules/**'",
"svgo": "svgo images/**/*.svg",
"svgo": "svgo --multipass images/**/*.svg",
"transpile": "run-p check:types build:esbuild",
"check:types": "tsc --noEmit",
"clean-transpile-once": "rimraf app/**/*.js app/*.js sticker-creator/**/*.js sticker-creator/*.js ts/**/*.js ts/*.js tsconfig.tsbuildinfo",

View file

@ -33,7 +33,6 @@
type="application/javascript"
src="ts/windows/applyTheme.js"
></script>
<script type="text/javascript" src="ts/set_os_class.js"></script>
<script type="application/javascript" src="ts/windows/init.js"></script>
</body>
</html>

View file

@ -14,6 +14,5 @@
<body>
<div id="root"></div>
<script type="text/javascript" src="../../js/components.js"></script>
<script type="text/javascript" src="../../ts/set_os_class.js"></script>
</body>
</html>

View file

@ -7,7 +7,7 @@
height: 20px;
width: 20px;
input[type='checkbox'] {
input {
cursor: pointer;
height: 0;
position: absolute;
@ -39,25 +39,6 @@
}
}
&:checked {
&::before {
background: $color-ultramarine;
border: 1.5px solid $color-ultramarine;
}
&::after {
border: solid $color-white;
border-width: 0 2px 2px 0;
content: '';
display: block;
height: 11px;
left: 7px;
position: absolute;
top: 3px;
transform: rotate(45deg);
width: 6px;
}
}
&:disabled {
cursor: inherit;
}
@ -87,6 +68,98 @@
}
}
}
&:checked {
&::after {
content: '';
display: block;
position: absolute;
}
}
}
input[type='checkbox'] {
&:checked {
&::before {
background: $color-ultramarine;
border: 1.5px solid $color-ultramarine;
}
&::after {
border: solid $color-white;
border-width: 0 2px 2px 0;
height: 11px;
left: 7px;
top: 3px;
transform: rotate(45deg);
width: 6px;
}
}
}
input[type='radio'] {
&:checked {
&::before {
border: 2px solid $color-ultramarine;
}
&::after {
background: $color-ultramarine;
top: 4px;
left: 4px;
width: 12px;
height: 12px;
border-radius: 6px;
}
}
}
&--small {
height: 18px;
width: 18px;
input {
&::before {
height: 18px;
width: 18px;
}
}
input[type='checkbox'] {
&:checked {
&::before {
background: $color-ultramarine;
border: 1.5px solid $color-ultramarine;
}
&::after {
border: solid $color-white;
border-width: 0 2px 2px 0;
height: 10px;
left: 7px;
top: 3px;
transform: rotate(45deg);
width: 5px;
}
}
}
input[type='radio'] {
&:checked {
&::before {
border: 2px solid $color-ultramarine;
}
&::after {
background: $color-ultramarine;
top: 4px;
left: 4px;
width: 10px;
height: 10px;
border-radius: 5px;
}
}
}
}
}
}

View file

@ -15,6 +15,7 @@
.Preferences {
display: flex;
overflow: hidden;
user-select: none;
@include light-theme {
background: $color-white;
}
@ -23,7 +24,7 @@
}
&__page-selector {
padding-top: 76px;
padding-top: calc(24px + var(--title-bar-drag-area-height));
min-width: 240px;
@include light-theme {
background: $color-gray-02;
@ -133,10 +134,19 @@
@include font-body-1-bold;
align-items: center;
display: flex;
height: 76px;
padding: 42px 0 14px 0;
height: 48px;
margin-top: var(--title-bar-drag-area-height);
margin-bottom: 24px;
text-align: center;
border-bottom: 1px solid $color-gray-15;
@include light-theme {
border-color: $color-gray-15;
}
@include dark-theme {
border-color: $color-gray-65;
}
&--header {
flex-grow: 1;
text-align: center;
@ -144,7 +154,7 @@
}
&__settings-row {
padding-bottom: 12px;
padding-bottom: 20px;
h3 {
@include font-body-1-bold;
@ -164,6 +174,30 @@
margin-bottom: 24px;
}
&__link {
@include button-reset;
padding: 0px 0 28px 0;
width: 100%;
h3 {
@include font-body-1;
font-weight: 400;
margin: 0;
margin-bottom: 8px;
}
}
&__link:not(:last-child) {
border-bottom: 1px solid $color-gray-15;
@include light-theme {
border-color: $color-gray-15;
}
@include dark-theme {
border-color: $color-gray-65;
}
margin-bottom: 24px;
}
&__control {
align-items: center;
display: flex;
@ -255,4 +289,15 @@
&__stories-off {
min-width: 140px;
}
&__settings-radio__label {
display: flex;
flex-direction: row;
gap: 16px;
height: 40px;
align-items: center;
&:last-child {
margin-bottom: 8px;
}
}
}

View file

@ -36,3 +36,13 @@ export const getName = (): string => {
}
return 'Linux';
};
export const getClassName = (): string => {
if (isMacOS()) {
return 'os-macos';
}
if (isWindows()) {
return 'os-windows';
}
return 'os-linux';
};

View file

@ -5,7 +5,7 @@ import React from 'react';
import { action } from '@storybook/addon-actions';
import type { Props } from './CircleCheckbox';
import { CircleCheckbox } from './CircleCheckbox';
import { CircleCheckbox, Variant } from './CircleCheckbox';
const createProps = (): Props => ({
checked: false,
@ -28,3 +28,53 @@ export function Checked(): JSX.Element {
export function Disabled(): JSX.Element {
return <CircleCheckbox {...createProps()} disabled />;
}
export function SmallNormal(): JSX.Element {
return <CircleCheckbox variant={Variant.Small} {...createProps()} />;
}
export function SmallChecked(): JSX.Element {
return <CircleCheckbox variant={Variant.Small} {...createProps()} checked />;
}
export function SmallDisabled(): JSX.Element {
return <CircleCheckbox variant={Variant.Small} {...createProps()} disabled />;
}
export function RadioNormal(): JSX.Element {
return <CircleCheckbox isRadio {...createProps()} />;
}
export function RadioChecked(): JSX.Element {
return <CircleCheckbox isRadio {...createProps()} checked />;
}
export function RadioDisabled(): JSX.Element {
return <CircleCheckbox isRadio {...createProps()} disabled />;
}
export function SmallRadioNormal(): JSX.Element {
return <CircleCheckbox variant={Variant.Small} isRadio {...createProps()} />;
}
export function SmallRadioChecked(): JSX.Element {
return (
<CircleCheckbox
variant={Variant.Small}
isRadio
{...createProps()}
checked
/>
);
}
export function SmallRadioDisabled(): JSX.Element {
return (
<CircleCheckbox
variant={Variant.Small}
isRadio
{...createProps()}
disabled
/>
);
}

View file

@ -2,15 +2,24 @@
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import classNames from 'classnames';
import { getClassNamesFor } from '../util/getClassNamesFor';
import { missingCaseError } from '../util/missingCaseError';
export enum Variant {
Normal = 'Normal',
Small = 'Small',
}
export type Props = {
id?: string;
variant?: Variant;
checked?: boolean;
disabled?: boolean;
isRadio?: boolean;
name?: string;
moduleClassName?: string;
onChange?: (value: boolean) => unknown;
onClick?: () => unknown;
};
@ -24,17 +33,28 @@ export type Props = {
*/
export function CircleCheckbox({
id,
variant = Variant.Normal,
checked,
disabled,
isRadio,
moduleClassName,
name,
onChange,
onClick,
}: Props): JSX.Element {
const getClassName = getClassNamesFor('CircleCheckbox');
const getClassName = getClassNamesFor('CircleCheckbox', moduleClassName);
let variantModifier: string;
if (variant === Variant.Normal) {
variantModifier = getClassName('__checkbox--normal');
} else if (variant === Variant.Small) {
variantModifier = getClassName('__checkbox--small');
} else {
throw missingCaseError(variant);
}
return (
<div className={getClassName('__checkbox')}>
<div className={classNames(getClassName('__checkbox'), variantModifier)}>
<input
checked={Boolean(checked)}
disabled={disabled}

View file

@ -96,7 +96,7 @@ const getDefaultArgs = (): PropsDataType => ({
isAutoLaunchSupported: true,
isHideMenuBarSupported: true,
isNotificationAttentionSupported: true,
isPhoneNumberSharingSupported: false,
isPhoneNumberSharingSupported: true,
isSyncSupported: true,
isSystemTraySupported: true,
isMinimizeToAndStartInSystemTraySupported: true,
@ -163,6 +163,8 @@ export default {
onSpellCheckChange: { action: true },
onThemeChange: { action: true },
onUniversalExpireTimerChange: { action: true },
onWhoCanSeeMeChange: { action: true },
onWhoCanFindMeChange: { action: true },
onZoomFactorChange: { action: true },
removeCustomColor: { action: true },
removeCustomColorOnConversations: { action: true },
@ -195,3 +197,23 @@ CustomUniversalExpireTimer.args = {
CustomUniversalExpireTimer.story = {
name: 'Custom universalExpireTimer',
};
export const PNPSharingDisabled = Template.bind({});
PNPSharingDisabled.args = {
whoCanSeeMe: PhoneNumberSharingMode.Nobody,
whoCanFindMe: PhoneNumberDiscoverability.Discoverable,
isPhoneNumberSharingSupported: true,
};
PNPSharingDisabled.story = {
name: 'PNP Sharing Disabled',
};
export const PNPDiscoverabilityDisabled = Template.bind({});
PNPDiscoverabilityDisabled.args = {
whoCanSeeMe: PhoneNumberSharingMode.Nobody,
whoCanFindMe: PhoneNumberDiscoverability.NotDiscoverable,
isPhoneNumberSharingSupported: true,
};
PNPDiscoverabilityDisabled.story = {
name: 'PNP Discoverability Disabled',
};

View file

@ -2,10 +2,11 @@
// SPDX-License-Identifier: AGPL-3.0-only
import type { ReactNode } from 'react';
import React, { useEffect, useState, useCallback } from 'react';
import React, { useEffect, useState, useCallback, useMemo } from 'react';
import { noop } from 'lodash';
import classNames from 'classnames';
import type { AudioDevice } from '@signalapp/ringrtc';
import uuid from 'uuid';
import type { MediaDeviceSettings } from '../types/Calling';
import type {
@ -17,6 +18,10 @@ import type { ThemeSettingType } from '../types/StorageUIKeys';
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 type { ConversationType } from '../state/ducks/conversations';
import type {
@ -159,6 +164,8 @@ type PropsFunctionType = {
onSpellCheckChange: CheckboxChangeHandlerType;
onThemeChange: SelectChangeHandlerType<ThemeType>;
onUniversalExpireTimerChange: SelectChangeHandlerType<number>;
onWhoCanSeeMeChange: SelectChangeHandlerType<PhoneNumberSharingMode>;
onWhoCanFindMeChange: SelectChangeHandlerType<PhoneNumberDiscoverability>;
onZoomFactorChange: SelectChangeHandlerType<ZoomFactorType>;
// Localization
@ -178,6 +185,7 @@ enum Page {
// Sub pages
ChatColor = 'ChatColor',
PNP = 'PNP',
}
const DEFAULT_ZOOM_FACTORS = [
@ -278,6 +286,8 @@ export function Preferences({
onSpellCheckChange,
onThemeChange,
onUniversalExpireTimerChange,
onWhoCanSeeMeChange,
onWhoCanFindMeChange,
onZoomFactorChange,
removeCustomColor,
removeCustomColorOnConversations,
@ -845,6 +855,20 @@ export function Preferences({
{i18n('Preferences__button--privacy')}
</div>
</div>
{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}
<SettingsRow>
<Control
left={i18n('Preferences--blocked')}
@ -859,61 +883,6 @@ export function Preferences({
}
/>
</SettingsRow>
{isPhoneNumberSharingSupported ? (
<SettingsRow title={i18n('Preferences__who-can--title')}>
<Control
left={i18n('Preferences--see-me')}
right={
<Select
ariaLabel={i18n('Preferences--see-me')}
disabled
onChange={noop}
options={[
{
text: i18n('Preferences__who-can--everybody'),
value: PhoneNumberSharingMode.Everybody,
},
{
text: i18n('Preferences__who-can--contacts'),
value: PhoneNumberSharingMode.ContactsOnly,
},
{
text: i18n('Preferences__who-can--nobody'),
value: PhoneNumberSharingMode.Nobody,
},
]}
value={whoCanSeeMe}
/>
}
/>
<Control
left={i18n('Preferences--find-me')}
right={
<Select
ariaLabel={i18n('Preferences--find-me')}
disabled
onChange={noop}
options={[
{
text: i18n('Preferences__who-can--everybody'),
value: PhoneNumberDiscoverability.Discoverable,
},
{
text: i18n('Preferences__who-can--nobody'),
value: PhoneNumberDiscoverability.NotDiscoverable,
},
]}
value={whoCanFindMe}
/>
}
/>
<div className="Preferences__padding">
<div className="Preferences__description">
{i18n('Preferences__privacy--description')}
</div>
</div>
</SettingsRow>
) : null}
<SettingsRow title={i18n('Preferences--messaging')}>
<Checkbox
checked={hasReadReceipts}
@ -1120,6 +1089,82 @@ export function Preferences({
/>
</>
);
} else if (page === Page.PNP) {
settings = (
<>
<div className="Preferences__title">
<button
aria-label={i18n('goBack')}
className="Preferences__back-icon"
onClick={() => setPage(Page.Privacy)}
type="button"
/>
<div className="Preferences__title--header">
{i18n('icu:Preferences__pnp--page-title')}
</div>
</div>
<SettingsRow title={i18n('icu:Preferences__pnp__sharing--title')}>
<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>
</>
);
}
return (
@ -1128,6 +1173,7 @@ export function Preferences({
theme={theme}
executeMenuRole={executeMenuRole}
>
<div className="module-title-bar-drag-area" />
<div className="Preferences">
<div className="Preferences__page-selector">
<button
@ -1191,7 +1237,8 @@ export function Preferences({
className={classNames({
Preferences__button: true,
'Preferences__button--privacy': true,
'Preferences__button--selected': page === Page.Privacy,
'Preferences__button--selected':
page === Page.Privacy || page === Page.PNP,
})}
onClick={() => setPage(Page.Privacy)}
>
@ -1207,12 +1254,14 @@ export function Preferences({
function SettingsRow({
children,
title,
className,
}: {
children: ReactNode;
title?: string;
className?: string;
}): JSX.Element {
return (
<div className="Preferences__settings-row">
<div className={classNames('Preferences__settings-row', className)}>
{title && <h3 className="Preferences__padding">{title}</h3>}
{children}
</div>
@ -1250,6 +1299,49 @@ function Control({
return <div className="Preferences__control">{content}</div>;
}
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>
);
}
function localizeDefault(i18n: LocalizerType, deviceLabel: string): string {
return deviceLabel.toLowerCase().startsWith('default')
? deviceLabel.replace(

View file

@ -2,11 +2,12 @@
// SPDX-License-Identifier: AGPL-3.0-only
import React, { useMemo, useState } from 'react';
import uuid from 'uuid';
import type { LocalizerType } from '../../../types/Util';
import { getMuteOptions } from '../../../util/getMuteOptions';
import { parseIntOrThrow } from '../../../util/parseIntOrThrow';
import { Checkbox } from '../../Checkbox';
import { CircleCheckbox, Variant } from '../../CircleCheckbox';
import { Modal } from '../../Modal';
import { Button, ButtonVariant } from '../../Button';
@ -30,11 +31,13 @@ export function ConversationNotificationsModal({
}: PropsType): JSX.Element {
const muteOptions = useMemo(
() =>
getMuteOptions(muteExpiresAt, i18n).map(({ disabled, name, value }) => ({
disabled,
text: name,
value,
})),
getMuteOptions(muteExpiresAt, i18n)
.map(({ disabled, name, value }) => ({
disabled,
text: name,
value,
}))
.filter(x => x.value > 0),
[i18n, muteExpiresAt]
);
@ -49,6 +52,10 @@ export function ConversationNotificationsModal({
onClose();
};
const htmlIds = useMemo(() => {
return Array.from({ length: muteOptions.length }, () => uuid());
}, [muteOptions.length]);
return (
<Modal
modalName="ConversationNotificationsModal"
@ -67,20 +74,25 @@ export function ConversationNotificationsModal({
</>
}
>
{muteOptions
.filter(x => x.value > 0)
.map(option => (
<Checkbox
{muteOptions.map((option, i) => (
<label
className="Preferences__settings-radio__label"
key={htmlIds[i]}
htmlFor={htmlIds[i]}
>
<CircleCheckbox
id={htmlIds[i]}
checked={muteExpirationValue === option.value}
variant={Variant.Small}
disabled={option.disabled}
isRadio
key={option.value}
label={option.text}
moduleClassName="ConversationDetails__radio"
name="mute"
onChange={value => value && setMuteExpirationValue(option.value)}
/>
))}
{option.text}
</label>
))}
</Modal>
);
}

View file

@ -67,8 +67,6 @@ export class SettingsChannel extends EventEmitter {
// Getters only. These are set by the primary device
this.installSetting('blockedCount', { setter: false });
this.installSetting('linkPreviewSetting', { setter: false });
this.installSetting('phoneNumberDiscoverabilitySetting', { setter: false });
this.installSetting('phoneNumberSharingSetting', { setter: false });
this.installSetting('readReceiptSetting', { setter: false });
this.installSetting('typingIndicatorSetting', { setter: false });
@ -109,6 +107,9 @@ export class SettingsChannel extends EventEmitter {
this.installSetting('hasStoriesDisabled');
this.installSetting('zoomFactor');
this.installSetting('phoneNumberDiscoverabilitySetting');
this.installSetting('phoneNumberSharingSetting');
installPermissionsHandler({ session, userConfig });
// These ones are different because its single source of truth is userConfig,

View file

@ -296,9 +296,6 @@ export function toAccountRecord(
PHONE_NUMBER_SHARING_MODE_ENUM.EVERYBODY;
break;
case PhoneNumberSharingMode.ContactsOnly:
accountRecord.phoneNumberSharingMode =
PHONE_NUMBER_SHARING_MODE_ENUM.CONTACTS_ONLY;
break;
case PhoneNumberSharingMode.Nobody:
accountRecord.phoneNumberSharingMode =
PHONE_NUMBER_SHARING_MODE_ENUM.NOBODY;
@ -1222,8 +1219,6 @@ export async function mergeAccountRecord(
phoneNumberSharingModeToStore = PhoneNumberSharingMode.Everybody;
break;
case PHONE_NUMBER_SHARING_MODE_ENUM.CONTACTS_ONLY:
phoneNumberSharingModeToStore = PhoneNumberSharingMode.ContactsOnly;
break;
case PHONE_NUMBER_SHARING_MODE_ENUM.NOBODY:
phoneNumberSharingModeToStore = PhoneNumberSharingMode.Nobody;
break;

View file

@ -7,7 +7,7 @@ import type { MenuItemConstructorOptions } from 'electron';
import type { MenuActionType } from '../../types/menu';
import { App } from '../../components/App';
import { getName as getOSName } from '../../OS';
import { getName as getOSName, getClassName as getOSClassName } from '../../OS';
import { SmartCallManager } from './CallManager';
import { SmartGlobalModalContainer } from './GlobalModalContainer';
import { SmartLightbox } from './Lightbox';
@ -39,18 +39,6 @@ function renderInbox(): JSX.Element {
const mapStateToProps = (state: StateType) => {
const i18n = getIntl(state);
const { osName } = state.user;
let osClassName = '';
if (osName === 'windows') {
osClassName = 'os-windows';
} else if (osName === 'macos') {
osClassName = 'os-macos';
} else if (osName === 'linux') {
osClassName = 'os-linux';
}
return {
...state.app,
i18n,
@ -60,7 +48,7 @@ const mapStateToProps = (state: StateType) => {
menuOptions: getMenuOptions(state),
hasCustomTitleBar: window.SignalContext.OS.hasCustomTitleBar(),
OS: getOSName(),
osClassName,
osClassName: getOSClassName(),
hideMenuBar: getHideMenuBar(state),
renderCallManager: () => (
<ModalContainer className="module-calling__modal-container">

View file

@ -494,6 +494,7 @@ const URL_CALLS = {
keys: 'v2/keys',
messages: 'v1/messages',
multiRecipient: 'v1/messages/multi_recipient',
phoneNumberDiscoverability: 'v2/accounts/phone_number_discoverability',
profile: 'v1/profile',
registerCapabilities: 'v1/devices/capabilities',
reportMessage: 'v1/messages/report',
@ -541,6 +542,9 @@ const WEBSOCKET_CALLS = new Set<keyof typeof URL_CALLS>([
// Storage
'storageToken',
// Account V2
'phoneNumberDiscoverability',
]);
type InitializeOptionsType = {
@ -979,6 +983,7 @@ export type WebAPIType = {
urgent?: boolean;
}
) => Promise<MultiRecipient200ResponseType>;
setPhoneNumberDiscoverability: (newValue: boolean) => Promise<void>;
setSignedPreKey: (
signedPreKey: SignedPreKeyType,
uuidKind: UUIDKind
@ -1272,6 +1277,7 @@ export function initialize({
sendMessages,
sendMessagesUnauth,
sendWithSenderKey,
setPhoneNumberDiscoverability,
setSignedPreKey,
startRegistration,
unregisterRequestHandler,
@ -2027,6 +2033,16 @@ export function initialize({
});
}
async function setPhoneNumberDiscoverability(newValue: boolean) {
await _ajax({
call: 'phoneNumberDiscoverability',
httpType: 'PUT',
jsonData: {
discoverableByPhoneNumber: newValue,
},
});
}
async function setSignedPreKey(
signedPreKey: SignedPreKeyType,
uuidKind: UUIDKind

View file

@ -28,7 +28,7 @@ import { renderClearingDataView } from '../shims/renderClearingDataView';
import * as universalExpireTimer from './universalExpireTimer';
import { PhoneNumberDiscoverability } from './phoneNumberDiscoverability';
import { PhoneNumberSharingMode } from './phoneNumberSharingMode';
import { assertDev } from './assert';
import { strictAssert, assertDev } from './assert';
import * as durations from './durations';
import type { DurationInSeconds } from './durations';
import { isPhoneNumberSharingEnabled } from './isPhoneNumberSharingEnabled';
@ -136,8 +136,6 @@ type ValuesWithSetters = Omit<
| 'blockedCount'
| 'defaultConversationColor'
| 'linkPreviewSetting'
| 'phoneNumberDiscoverabilitySetting'
| 'phoneNumberSharingSetting'
| 'readReceiptSetting'
| 'typingIndicatorSetting'
| 'deviceName'
@ -177,6 +175,18 @@ export type IPCEventsType = IPCEventsGettersType &
export function createIPCEvents(
overrideEvents: Partial<IPCEventsType> = {}
): IPCEventsType {
const setPhoneNumberDiscoverabilitySetting = async (
newValue: PhoneNumberDiscoverability
): Promise<void> => {
strictAssert(window.textsecure.server, 'WebAPI must be available');
await window.storage.put('phoneNumberDiscoverability', newValue);
await window.textsecure.server.setPhoneNumberDiscoverability(
newValue === PhoneNumberDiscoverability.Discoverable
);
const account = window.ConversationController.getOurConversationOrThrow();
account.captureChange('phoneNumberDiscoverability');
};
return {
getDeviceName: () => window.textsecure.storage.user.getDeviceName(),
@ -185,6 +195,22 @@ export function createIPCEvents(
webFrame.setZoomFactor(zoomFactor);
},
setPhoneNumberDiscoverabilitySetting,
setPhoneNumberSharingSetting: async (newValue: PhoneNumberSharingMode) => {
const account = window.ConversationController.getOurConversationOrThrow();
const promises = new Array<Promise<void>>();
promises.push(window.storage.put('phoneNumberSharingMode', newValue));
if (newValue === PhoneNumberSharingMode.Everybody) {
promises.push(
setPhoneNumberDiscoverabilitySetting(
PhoneNumberDiscoverability.Discoverable
)
);
}
account.captureChange('phoneNumberSharingMode');
await Promise.all(promises);
},
getHasStoriesDisabled: () =>
window.storage.get('hasStoriesDisabled', false),
setHasStoriesDisabled: async (value: boolean) => {
@ -202,6 +228,8 @@ export function createIPCEvents(
},
setStoryViewReceiptsEnabled: async (value: boolean) => {
await window.storage.put('storyViewReceiptsEnabled', value);
const account = window.ConversationController.getOurConversationOrThrow();
account.captureChange('storyViewReceiptsEnabled');
},
getPreferredAudioInputDevice: () =>

View file

@ -4,5 +4,8 @@
import * as RemoteConfig from '../RemoteConfig';
export function isPhoneNumberSharingEnabled(): boolean {
return Boolean(RemoteConfig.isEnabled('desktop.internalUser'));
return Boolean(
RemoteConfig.isEnabled('desktop.internalUser') ||
RemoteConfig.isEnabled('desktop.pnp')
);
}

View file

@ -4,7 +4,6 @@
import type { ConversationAttributesType } from '../model-types.d';
import { makeEnumParser } from './enum';
import { isInSystemContacts } from './isInSystemContacts';
import { missingCaseError } from './missingCaseError';
import { isDirectConversation, isMe } from './whatTypeOfConversation';
@ -35,7 +34,6 @@ export const shouldSharePhoneNumberWith = (
case PhoneNumberSharingMode.Everybody:
return true;
case PhoneNumberSharingMode.ContactsOnly:
return isInSystemContacts(conversation);
case PhoneNumberSharingMode.Nobody:
return false;
default:

View file

@ -29,7 +29,13 @@ import { createSetting } from '../util/preload';
import { initialize as initializeLogging } from '../logging/set_up_renderer_logging';
import { waitForSettingsChange } from './waitForSettingsChange';
import { createNativeThemeListener } from '../context/createNativeThemeListener';
import { isWindows, isLinux, isMacOS, hasCustomTitleBar } from '../OS';
import {
isWindows,
isLinux,
isMacOS,
hasCustomTitleBar,
getClassName,
} from '../OS';
const activeWindowService = new ActiveWindowService();
activeWindowService.initialize(window.document, ipcRenderer);
@ -79,6 +85,7 @@ export type SignalContextType = {
isLinux: typeof isLinux;
isMacOS: typeof isMacOS;
hasCustomTitleBar: typeof hasCustomTitleBar;
getClassName: typeof getClassName;
};
config: RendererConfigType;
getAppInstance: () => string | undefined;
@ -108,6 +115,7 @@ export const SignalContext: SignalContextType = {
isLinux,
isMacOS,
hasCustomTitleBar,
getClassName,
},
bytes: new Bytes(),
config,

View file

@ -1,6 +1,11 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
document.body.classList.add(window.SignalContext.OS.getClassName());
if (window.SignalContext.OS.hasCustomTitleBar()) {
document.body.classList.add('os-has-custom-titlebar');
}
if (window.SignalContext.renderWindow) {
window.SignalContext.renderWindow();
} else {

View file

@ -15,6 +15,7 @@ import '../../backbone/reliable_trigger';
import type { FeatureFlagType } from '../../window.d';
import type { StorageAccessType } from '../../types/Storage.d';
import type { CdsLookupOptionsType } from '../../textsecure/WebAPI';
import { start as startConversationController } from '../../ConversationController';
import { MessageController } from '../../util/MessageController';
import { Environment, getEnvironment } from '../../environment';
@ -46,6 +47,8 @@ startConversationController();
if (!isProduction(window.SignalContext.getVersion())) {
const SignalDebug = {
Data: window.Signal.Data,
cdsLookup: (options: CdsLookupOptionsType) =>
window.textsecure.server?.cdsLookup(options),
getConversation: (id: string) => window.ConversationController.get(id),
getMessageById: (id: string) => window.MessageController.getById(id),
getReduxState: () => window.reduxStore.getState(),

View file

@ -24,12 +24,6 @@ installSetting('blockedCount', {
installSetting('linkPreviewSetting', {
setter: false,
});
installSetting('phoneNumberDiscoverabilitySetting', {
setter: false,
});
installSetting('phoneNumberSharingSetting', {
setter: false,
});
installSetting('readReceiptSetting', {
setter: false,
});
@ -63,6 +57,8 @@ installSetting('sentMediaQualitySetting');
installSetting('themeSetting');
installSetting('universalExpireTimer');
installSetting('zoomFactor');
installSetting('phoneNumberDiscoverabilitySetting');
installSetting('phoneNumberSharingSetting');
// Media Settings
installCallback('getAvailableIODevices');

View file

@ -58,12 +58,9 @@ const settingLinkPreview = createSetting('linkPreviewSetting', {
setter: false,
});
const settingPhoneNumberDiscoverability = createSetting(
'phoneNumberDiscoverabilitySetting',
{ setter: false }
'phoneNumberDiscoverabilitySetting'
);
const settingPhoneNumberSharing = createSetting('phoneNumberSharingSetting', {
setter: false,
});
const settingPhoneNumberSharing = createSetting('phoneNumberSharingSetting');
const settingReadReceipts = createSetting('readReceiptSetting', {
setter: false,
});
@ -363,6 +360,9 @@ const renderPreferences = async () => {
);
},
onWhoCanFindMeChange: reRender(settingPhoneNumberDiscoverability.setValue),
onWhoCanSeeMeChange: reRender(settingPhoneNumberSharing.setValue),
// Zoom factor change doesn't require immediate rerender since it will:
// 1. Update the zoom factor in the main window
// 2. Trigger `preferred-size-changed` in the main process