PNP Settings
This commit is contained in:
parent
5bcf71ef2c
commit
5d110964b9
27 changed files with 562 additions and 149 deletions
|
@ -5429,23 +5429,75 @@
|
||||||
},
|
},
|
||||||
"Preferences__who-can--title": {
|
"Preferences__who-can--title": {
|
||||||
"message": "Who can...",
|
"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": {
|
"Preferences__privacy--description": {
|
||||||
"message": "To change these settings, open the Signal app on your mobile device and navigate to Settings > Privacy",
|
"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": {
|
"Preferences__who-can--everybody": {
|
||||||
"message": "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": {
|
"Preferences__who-can--contacts": {
|
||||||
"message": "My 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": {
|
"Preferences__who-can--nobody": {
|
||||||
"message": "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": {
|
"Preferences--messaging": {
|
||||||
"message": "Messaging",
|
"message": "Messaging",
|
||||||
|
|
|
@ -29,7 +29,6 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<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>
|
<script type="application/javascript" src="ts/windows/init.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1268,7 +1268,7 @@ async function showSettingsWindow() {
|
||||||
frame: true,
|
frame: true,
|
||||||
resizable: false,
|
resizable: false,
|
||||||
title: getResolvedMessagesLocale().i18n('signalDesktopPreferences'),
|
title: getResolvedMessagesLocale().i18n('signalDesktopPreferences'),
|
||||||
titleBarStyle: nonMainTitleBarStyle,
|
titleBarStyle: mainTitleBarStyle,
|
||||||
titleBarOverlay,
|
titleBarOverlay,
|
||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true,
|
||||||
backgroundColor: await getBackgroundColor(),
|
backgroundColor: await getBackgroundColor(),
|
||||||
|
|
|
@ -33,7 +33,6 @@
|
||||||
type="application/javascript"
|
type="application/javascript"
|
||||||
src="ts/windows/applyTheme.js"
|
src="ts/windows/applyTheme.js"
|
||||||
></script>
|
></script>
|
||||||
<script type="text/javascript" src="ts/set_os_class.js"></script>
|
|
||||||
<script type="application/javascript" src="ts/windows/init.js"></script>
|
<script type="application/javascript" src="ts/windows/init.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -52,7 +52,7 @@
|
||||||
"danger:local": "./danger/danger.sh local --base main",
|
"danger:local": "./danger/danger.sh local --base main",
|
||||||
"danger:ci": "./danger/danger.sh ci --base origin/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/**'",
|
"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",
|
"transpile": "run-p check:types build:esbuild",
|
||||||
"check:types": "tsc --noEmit",
|
"check:types": "tsc --noEmit",
|
||||||
"clean-transpile-once": "rimraf app/**/*.js app/*.js sticker-creator/**/*.js sticker-creator/*.js ts/**/*.js ts/*.js tsconfig.tsbuildinfo",
|
"clean-transpile-once": "rimraf app/**/*.js app/*.js sticker-creator/**/*.js sticker-creator/*.js ts/**/*.js ts/*.js tsconfig.tsbuildinfo",
|
||||||
|
|
|
@ -33,7 +33,6 @@
|
||||||
type="application/javascript"
|
type="application/javascript"
|
||||||
src="ts/windows/applyTheme.js"
|
src="ts/windows/applyTheme.js"
|
||||||
></script>
|
></script>
|
||||||
<script type="text/javascript" src="ts/set_os_class.js"></script>
|
|
||||||
<script type="application/javascript" src="ts/windows/init.js"></script>
|
<script type="application/javascript" src="ts/windows/init.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -14,6 +14,5 @@
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="text/javascript" src="../../js/components.js"></script>
|
<script type="text/javascript" src="../../js/components.js"></script>
|
||||||
<script type="text/javascript" src="../../ts/set_os_class.js"></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
height: 20px;
|
height: 20px;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
|
|
||||||
input[type='checkbox'] {
|
input {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
height: 0;
|
height: 0;
|
||||||
position: absolute;
|
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 {
|
&:disabled {
|
||||||
cursor: inherit;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
.Preferences {
|
.Preferences {
|
||||||
display: flex;
|
display: flex;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
user-select: none;
|
||||||
@include light-theme {
|
@include light-theme {
|
||||||
background: $color-white;
|
background: $color-white;
|
||||||
}
|
}
|
||||||
|
@ -23,7 +24,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&__page-selector {
|
&__page-selector {
|
||||||
padding-top: 76px;
|
padding-top: calc(24px + var(--title-bar-drag-area-height));
|
||||||
min-width: 240px;
|
min-width: 240px;
|
||||||
@include light-theme {
|
@include light-theme {
|
||||||
background: $color-gray-02;
|
background: $color-gray-02;
|
||||||
|
@ -133,10 +134,19 @@
|
||||||
@include font-body-1-bold;
|
@include font-body-1-bold;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 76px;
|
height: 48px;
|
||||||
padding: 42px 0 14px 0;
|
margin-top: var(--title-bar-drag-area-height);
|
||||||
|
margin-bottom: 24px;
|
||||||
text-align: center;
|
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 {
|
&--header {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -144,7 +154,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&__settings-row {
|
&__settings-row {
|
||||||
padding-bottom: 12px;
|
padding-bottom: 20px;
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
@include font-body-1-bold;
|
@include font-body-1-bold;
|
||||||
|
@ -164,6 +174,30 @@
|
||||||
margin-bottom: 24px;
|
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 {
|
&__control {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -255,4 +289,15 @@
|
||||||
&__stories-off {
|
&__stories-off {
|
||||||
min-width: 140px;
|
min-width: 140px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__settings-radio__label {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 16px;
|
||||||
|
height: 40px;
|
||||||
|
align-items: center;
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
10
ts/OS.ts
10
ts/OS.ts
|
@ -36,3 +36,13 @@ export const getName = (): string => {
|
||||||
}
|
}
|
||||||
return 'Linux';
|
return 'Linux';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getClassName = (): string => {
|
||||||
|
if (isMacOS()) {
|
||||||
|
return 'os-macos';
|
||||||
|
}
|
||||||
|
if (isWindows()) {
|
||||||
|
return 'os-windows';
|
||||||
|
}
|
||||||
|
return 'os-linux';
|
||||||
|
};
|
||||||
|
|
|
@ -5,7 +5,7 @@ import React from 'react';
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from '@storybook/addon-actions';
|
||||||
|
|
||||||
import type { Props } from './CircleCheckbox';
|
import type { Props } from './CircleCheckbox';
|
||||||
import { CircleCheckbox } from './CircleCheckbox';
|
import { CircleCheckbox, Variant } from './CircleCheckbox';
|
||||||
|
|
||||||
const createProps = (): Props => ({
|
const createProps = (): Props => ({
|
||||||
checked: false,
|
checked: false,
|
||||||
|
@ -28,3 +28,53 @@ export function Checked(): JSX.Element {
|
||||||
export function Disabled(): JSX.Element {
|
export function Disabled(): JSX.Element {
|
||||||
return <CircleCheckbox {...createProps()} disabled />;
|
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
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -2,15 +2,24 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { getClassNamesFor } from '../util/getClassNamesFor';
|
import { getClassNamesFor } from '../util/getClassNamesFor';
|
||||||
|
import { missingCaseError } from '../util/missingCaseError';
|
||||||
|
|
||||||
|
export enum Variant {
|
||||||
|
Normal = 'Normal',
|
||||||
|
Small = 'Small',
|
||||||
|
}
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
id?: string;
|
id?: string;
|
||||||
|
variant?: Variant;
|
||||||
checked?: boolean;
|
checked?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
isRadio?: boolean;
|
isRadio?: boolean;
|
||||||
name?: string;
|
name?: string;
|
||||||
|
moduleClassName?: string;
|
||||||
onChange?: (value: boolean) => unknown;
|
onChange?: (value: boolean) => unknown;
|
||||||
onClick?: () => unknown;
|
onClick?: () => unknown;
|
||||||
};
|
};
|
||||||
|
@ -24,17 +33,28 @@ export type Props = {
|
||||||
*/
|
*/
|
||||||
export function CircleCheckbox({
|
export function CircleCheckbox({
|
||||||
id,
|
id,
|
||||||
|
variant = Variant.Normal,
|
||||||
checked,
|
checked,
|
||||||
disabled,
|
disabled,
|
||||||
isRadio,
|
isRadio,
|
||||||
|
moduleClassName,
|
||||||
name,
|
name,
|
||||||
onChange,
|
onChange,
|
||||||
onClick,
|
onClick,
|
||||||
}: Props): JSX.Element {
|
}: 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 (
|
return (
|
||||||
<div className={getClassName('__checkbox')}>
|
<div className={classNames(getClassName('__checkbox'), variantModifier)}>
|
||||||
<input
|
<input
|
||||||
checked={Boolean(checked)}
|
checked={Boolean(checked)}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|
|
@ -96,7 +96,7 @@ const getDefaultArgs = (): PropsDataType => ({
|
||||||
isAutoLaunchSupported: true,
|
isAutoLaunchSupported: true,
|
||||||
isHideMenuBarSupported: true,
|
isHideMenuBarSupported: true,
|
||||||
isNotificationAttentionSupported: true,
|
isNotificationAttentionSupported: true,
|
||||||
isPhoneNumberSharingSupported: false,
|
isPhoneNumberSharingSupported: true,
|
||||||
isSyncSupported: true,
|
isSyncSupported: true,
|
||||||
isSystemTraySupported: true,
|
isSystemTraySupported: true,
|
||||||
isMinimizeToAndStartInSystemTraySupported: true,
|
isMinimizeToAndStartInSystemTraySupported: true,
|
||||||
|
@ -163,6 +163,8 @@ export default {
|
||||||
onSpellCheckChange: { action: true },
|
onSpellCheckChange: { action: true },
|
||||||
onThemeChange: { action: true },
|
onThemeChange: { action: true },
|
||||||
onUniversalExpireTimerChange: { action: true },
|
onUniversalExpireTimerChange: { action: true },
|
||||||
|
onWhoCanSeeMeChange: { action: true },
|
||||||
|
onWhoCanFindMeChange: { action: true },
|
||||||
onZoomFactorChange: { action: true },
|
onZoomFactorChange: { action: true },
|
||||||
removeCustomColor: { action: true },
|
removeCustomColor: { action: true },
|
||||||
removeCustomColorOnConversations: { action: true },
|
removeCustomColorOnConversations: { action: true },
|
||||||
|
@ -195,3 +197,23 @@ CustomUniversalExpireTimer.args = {
|
||||||
CustomUniversalExpireTimer.story = {
|
CustomUniversalExpireTimer.story = {
|
||||||
name: 'Custom universalExpireTimer',
|
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',
|
||||||
|
};
|
||||||
|
|
|
@ -2,10 +2,11 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import type { ReactNode } from 'react';
|
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 { noop } from 'lodash';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { AudioDevice } from '@signalapp/ringrtc';
|
import type { AudioDevice } from '@signalapp/ringrtc';
|
||||||
|
import uuid from 'uuid';
|
||||||
|
|
||||||
import type { MediaDeviceSettings } from '../types/Calling';
|
import type { MediaDeviceSettings } from '../types/Calling';
|
||||||
import type {
|
import type {
|
||||||
|
@ -17,6 +18,10 @@ import type { ThemeSettingType } from '../types/StorageUIKeys';
|
||||||
import { Button, ButtonVariant } from './Button';
|
import { Button, ButtonVariant } from './Button';
|
||||||
import { ChatColorPicker } from './ChatColorPicker';
|
import { ChatColorPicker } from './ChatColorPicker';
|
||||||
import { Checkbox } from './Checkbox';
|
import { Checkbox } from './Checkbox';
|
||||||
|
import {
|
||||||
|
CircleCheckbox,
|
||||||
|
Variant as CircleCheckboxVariant,
|
||||||
|
} from './CircleCheckbox';
|
||||||
import { ConfirmationDialog } from './ConfirmationDialog';
|
import { ConfirmationDialog } from './ConfirmationDialog';
|
||||||
import type { ConversationType } from '../state/ducks/conversations';
|
import type { ConversationType } from '../state/ducks/conversations';
|
||||||
import type {
|
import type {
|
||||||
|
@ -159,6 +164,8 @@ type PropsFunctionType = {
|
||||||
onSpellCheckChange: CheckboxChangeHandlerType;
|
onSpellCheckChange: CheckboxChangeHandlerType;
|
||||||
onThemeChange: SelectChangeHandlerType<ThemeType>;
|
onThemeChange: SelectChangeHandlerType<ThemeType>;
|
||||||
onUniversalExpireTimerChange: SelectChangeHandlerType<number>;
|
onUniversalExpireTimerChange: SelectChangeHandlerType<number>;
|
||||||
|
onWhoCanSeeMeChange: SelectChangeHandlerType<PhoneNumberSharingMode>;
|
||||||
|
onWhoCanFindMeChange: SelectChangeHandlerType<PhoneNumberDiscoverability>;
|
||||||
onZoomFactorChange: SelectChangeHandlerType<ZoomFactorType>;
|
onZoomFactorChange: SelectChangeHandlerType<ZoomFactorType>;
|
||||||
|
|
||||||
// Localization
|
// Localization
|
||||||
|
@ -178,6 +185,7 @@ enum Page {
|
||||||
|
|
||||||
// Sub pages
|
// Sub pages
|
||||||
ChatColor = 'ChatColor',
|
ChatColor = 'ChatColor',
|
||||||
|
PNP = 'PNP',
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_ZOOM_FACTORS = [
|
const DEFAULT_ZOOM_FACTORS = [
|
||||||
|
@ -278,6 +286,8 @@ export function Preferences({
|
||||||
onSpellCheckChange,
|
onSpellCheckChange,
|
||||||
onThemeChange,
|
onThemeChange,
|
||||||
onUniversalExpireTimerChange,
|
onUniversalExpireTimerChange,
|
||||||
|
onWhoCanSeeMeChange,
|
||||||
|
onWhoCanFindMeChange,
|
||||||
onZoomFactorChange,
|
onZoomFactorChange,
|
||||||
removeCustomColor,
|
removeCustomColor,
|
||||||
removeCustomColorOnConversations,
|
removeCustomColorOnConversations,
|
||||||
|
@ -845,6 +855,20 @@ export function Preferences({
|
||||||
{i18n('Preferences__button--privacy')}
|
{i18n('Preferences__button--privacy')}
|
||||||
</div>
|
</div>
|
||||||
</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>
|
<SettingsRow>
|
||||||
<Control
|
<Control
|
||||||
left={i18n('Preferences--blocked')}
|
left={i18n('Preferences--blocked')}
|
||||||
|
@ -859,61 +883,6 @@ export function Preferences({
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</SettingsRow>
|
</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')}>
|
<SettingsRow title={i18n('Preferences--messaging')}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={hasReadReceipts}
|
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 (
|
return (
|
||||||
|
@ -1128,6 +1173,7 @@ export function Preferences({
|
||||||
theme={theme}
|
theme={theme}
|
||||||
executeMenuRole={executeMenuRole}
|
executeMenuRole={executeMenuRole}
|
||||||
>
|
>
|
||||||
|
<div className="module-title-bar-drag-area" />
|
||||||
<div className="Preferences">
|
<div className="Preferences">
|
||||||
<div className="Preferences__page-selector">
|
<div className="Preferences__page-selector">
|
||||||
<button
|
<button
|
||||||
|
@ -1191,7 +1237,8 @@ export function Preferences({
|
||||||
className={classNames({
|
className={classNames({
|
||||||
Preferences__button: true,
|
Preferences__button: true,
|
||||||
'Preferences__button--privacy': true,
|
'Preferences__button--privacy': true,
|
||||||
'Preferences__button--selected': page === Page.Privacy,
|
'Preferences__button--selected':
|
||||||
|
page === Page.Privacy || page === Page.PNP,
|
||||||
})}
|
})}
|
||||||
onClick={() => setPage(Page.Privacy)}
|
onClick={() => setPage(Page.Privacy)}
|
||||||
>
|
>
|
||||||
|
@ -1207,12 +1254,14 @@ export function Preferences({
|
||||||
function SettingsRow({
|
function SettingsRow({
|
||||||
children,
|
children,
|
||||||
title,
|
title,
|
||||||
|
className,
|
||||||
}: {
|
}: {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
title?: string;
|
title?: string;
|
||||||
|
className?: string;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div className="Preferences__settings-row">
|
<div className={classNames('Preferences__settings-row', className)}>
|
||||||
{title && <h3 className="Preferences__padding">{title}</h3>}
|
{title && <h3 className="Preferences__padding">{title}</h3>}
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1250,6 +1299,49 @@ function Control({
|
||||||
return <div className="Preferences__control">{content}</div>;
|
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 {
|
function localizeDefault(i18n: LocalizerType, deviceLabel: string): string {
|
||||||
return deviceLabel.toLowerCase().startsWith('default')
|
return deviceLabel.toLowerCase().startsWith('default')
|
||||||
? deviceLabel.replace(
|
? deviceLabel.replace(
|
||||||
|
|
|
@ -2,11 +2,12 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React, { useMemo, useState } from 'react';
|
import React, { useMemo, useState } from 'react';
|
||||||
|
import uuid from 'uuid';
|
||||||
|
|
||||||
import type { LocalizerType } from '../../../types/Util';
|
import type { LocalizerType } from '../../../types/Util';
|
||||||
import { getMuteOptions } from '../../../util/getMuteOptions';
|
import { getMuteOptions } from '../../../util/getMuteOptions';
|
||||||
import { parseIntOrThrow } from '../../../util/parseIntOrThrow';
|
import { parseIntOrThrow } from '../../../util/parseIntOrThrow';
|
||||||
import { Checkbox } from '../../Checkbox';
|
import { CircleCheckbox, Variant } from '../../CircleCheckbox';
|
||||||
import { Modal } from '../../Modal';
|
import { Modal } from '../../Modal';
|
||||||
import { Button, ButtonVariant } from '../../Button';
|
import { Button, ButtonVariant } from '../../Button';
|
||||||
|
|
||||||
|
@ -30,11 +31,13 @@ export function ConversationNotificationsModal({
|
||||||
}: PropsType): JSX.Element {
|
}: PropsType): JSX.Element {
|
||||||
const muteOptions = useMemo(
|
const muteOptions = useMemo(
|
||||||
() =>
|
() =>
|
||||||
getMuteOptions(muteExpiresAt, i18n).map(({ disabled, name, value }) => ({
|
getMuteOptions(muteExpiresAt, i18n)
|
||||||
|
.map(({ disabled, name, value }) => ({
|
||||||
disabled,
|
disabled,
|
||||||
text: name,
|
text: name,
|
||||||
value,
|
value,
|
||||||
})),
|
}))
|
||||||
|
.filter(x => x.value > 0),
|
||||||
[i18n, muteExpiresAt]
|
[i18n, muteExpiresAt]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -49,6 +52,10 @@ export function ConversationNotificationsModal({
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const htmlIds = useMemo(() => {
|
||||||
|
return Array.from({ length: muteOptions.length }, () => uuid());
|
||||||
|
}, [muteOptions.length]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
modalName="ConversationNotificationsModal"
|
modalName="ConversationNotificationsModal"
|
||||||
|
@ -67,19 +74,24 @@ export function ConversationNotificationsModal({
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{muteOptions
|
{muteOptions.map((option, i) => (
|
||||||
.filter(x => x.value > 0)
|
<label
|
||||||
.map(option => (
|
className="Preferences__settings-radio__label"
|
||||||
<Checkbox
|
key={htmlIds[i]}
|
||||||
|
htmlFor={htmlIds[i]}
|
||||||
|
>
|
||||||
|
<CircleCheckbox
|
||||||
|
id={htmlIds[i]}
|
||||||
checked={muteExpirationValue === option.value}
|
checked={muteExpirationValue === option.value}
|
||||||
|
variant={Variant.Small}
|
||||||
disabled={option.disabled}
|
disabled={option.disabled}
|
||||||
isRadio
|
isRadio
|
||||||
key={option.value}
|
|
||||||
label={option.text}
|
|
||||||
moduleClassName="ConversationDetails__radio"
|
moduleClassName="ConversationDetails__radio"
|
||||||
name="mute"
|
name="mute"
|
||||||
onChange={value => value && setMuteExpirationValue(option.value)}
|
onChange={value => value && setMuteExpirationValue(option.value)}
|
||||||
/>
|
/>
|
||||||
|
{option.text}
|
||||||
|
</label>
|
||||||
))}
|
))}
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|
|
@ -67,8 +67,6 @@ export class SettingsChannel extends EventEmitter {
|
||||||
// Getters only. These are set by the primary device
|
// Getters only. These are set by the primary device
|
||||||
this.installSetting('blockedCount', { setter: false });
|
this.installSetting('blockedCount', { setter: false });
|
||||||
this.installSetting('linkPreviewSetting', { setter: false });
|
this.installSetting('linkPreviewSetting', { setter: false });
|
||||||
this.installSetting('phoneNumberDiscoverabilitySetting', { setter: false });
|
|
||||||
this.installSetting('phoneNumberSharingSetting', { setter: false });
|
|
||||||
this.installSetting('readReceiptSetting', { setter: false });
|
this.installSetting('readReceiptSetting', { setter: false });
|
||||||
this.installSetting('typingIndicatorSetting', { setter: false });
|
this.installSetting('typingIndicatorSetting', { setter: false });
|
||||||
|
|
||||||
|
@ -109,6 +107,9 @@ export class SettingsChannel extends EventEmitter {
|
||||||
this.installSetting('hasStoriesDisabled');
|
this.installSetting('hasStoriesDisabled');
|
||||||
this.installSetting('zoomFactor');
|
this.installSetting('zoomFactor');
|
||||||
|
|
||||||
|
this.installSetting('phoneNumberDiscoverabilitySetting');
|
||||||
|
this.installSetting('phoneNumberSharingSetting');
|
||||||
|
|
||||||
installPermissionsHandler({ session, userConfig });
|
installPermissionsHandler({ session, userConfig });
|
||||||
|
|
||||||
// These ones are different because its single source of truth is userConfig,
|
// These ones are different because its single source of truth is userConfig,
|
||||||
|
|
|
@ -296,9 +296,6 @@ export function toAccountRecord(
|
||||||
PHONE_NUMBER_SHARING_MODE_ENUM.EVERYBODY;
|
PHONE_NUMBER_SHARING_MODE_ENUM.EVERYBODY;
|
||||||
break;
|
break;
|
||||||
case PhoneNumberSharingMode.ContactsOnly:
|
case PhoneNumberSharingMode.ContactsOnly:
|
||||||
accountRecord.phoneNumberSharingMode =
|
|
||||||
PHONE_NUMBER_SHARING_MODE_ENUM.CONTACTS_ONLY;
|
|
||||||
break;
|
|
||||||
case PhoneNumberSharingMode.Nobody:
|
case PhoneNumberSharingMode.Nobody:
|
||||||
accountRecord.phoneNumberSharingMode =
|
accountRecord.phoneNumberSharingMode =
|
||||||
PHONE_NUMBER_SHARING_MODE_ENUM.NOBODY;
|
PHONE_NUMBER_SHARING_MODE_ENUM.NOBODY;
|
||||||
|
@ -1222,8 +1219,6 @@ export async function mergeAccountRecord(
|
||||||
phoneNumberSharingModeToStore = PhoneNumberSharingMode.Everybody;
|
phoneNumberSharingModeToStore = PhoneNumberSharingMode.Everybody;
|
||||||
break;
|
break;
|
||||||
case PHONE_NUMBER_SHARING_MODE_ENUM.CONTACTS_ONLY:
|
case PHONE_NUMBER_SHARING_MODE_ENUM.CONTACTS_ONLY:
|
||||||
phoneNumberSharingModeToStore = PhoneNumberSharingMode.ContactsOnly;
|
|
||||||
break;
|
|
||||||
case PHONE_NUMBER_SHARING_MODE_ENUM.NOBODY:
|
case PHONE_NUMBER_SHARING_MODE_ENUM.NOBODY:
|
||||||
phoneNumberSharingModeToStore = PhoneNumberSharingMode.Nobody;
|
phoneNumberSharingModeToStore = PhoneNumberSharingMode.Nobody;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -7,7 +7,7 @@ import type { MenuItemConstructorOptions } from 'electron';
|
||||||
|
|
||||||
import type { MenuActionType } from '../../types/menu';
|
import type { MenuActionType } from '../../types/menu';
|
||||||
import { App } from '../../components/App';
|
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 { SmartCallManager } from './CallManager';
|
||||||
import { SmartGlobalModalContainer } from './GlobalModalContainer';
|
import { SmartGlobalModalContainer } from './GlobalModalContainer';
|
||||||
import { SmartLightbox } from './Lightbox';
|
import { SmartLightbox } from './Lightbox';
|
||||||
|
@ -39,18 +39,6 @@ function renderInbox(): JSX.Element {
|
||||||
const mapStateToProps = (state: StateType) => {
|
const mapStateToProps = (state: StateType) => {
|
||||||
const i18n = getIntl(state);
|
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 {
|
return {
|
||||||
...state.app,
|
...state.app,
|
||||||
i18n,
|
i18n,
|
||||||
|
@ -60,7 +48,7 @@ const mapStateToProps = (state: StateType) => {
|
||||||
menuOptions: getMenuOptions(state),
|
menuOptions: getMenuOptions(state),
|
||||||
hasCustomTitleBar: window.SignalContext.OS.hasCustomTitleBar(),
|
hasCustomTitleBar: window.SignalContext.OS.hasCustomTitleBar(),
|
||||||
OS: getOSName(),
|
OS: getOSName(),
|
||||||
osClassName,
|
osClassName: getOSClassName(),
|
||||||
hideMenuBar: getHideMenuBar(state),
|
hideMenuBar: getHideMenuBar(state),
|
||||||
renderCallManager: () => (
|
renderCallManager: () => (
|
||||||
<ModalContainer className="module-calling__modal-container">
|
<ModalContainer className="module-calling__modal-container">
|
||||||
|
|
|
@ -494,6 +494,7 @@ const URL_CALLS = {
|
||||||
keys: 'v2/keys',
|
keys: 'v2/keys',
|
||||||
messages: 'v1/messages',
|
messages: 'v1/messages',
|
||||||
multiRecipient: 'v1/messages/multi_recipient',
|
multiRecipient: 'v1/messages/multi_recipient',
|
||||||
|
phoneNumberDiscoverability: 'v2/accounts/phone_number_discoverability',
|
||||||
profile: 'v1/profile',
|
profile: 'v1/profile',
|
||||||
registerCapabilities: 'v1/devices/capabilities',
|
registerCapabilities: 'v1/devices/capabilities',
|
||||||
reportMessage: 'v1/messages/report',
|
reportMessage: 'v1/messages/report',
|
||||||
|
@ -541,6 +542,9 @@ const WEBSOCKET_CALLS = new Set<keyof typeof URL_CALLS>([
|
||||||
|
|
||||||
// Storage
|
// Storage
|
||||||
'storageToken',
|
'storageToken',
|
||||||
|
|
||||||
|
// Account V2
|
||||||
|
'phoneNumberDiscoverability',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
type InitializeOptionsType = {
|
type InitializeOptionsType = {
|
||||||
|
@ -979,6 +983,7 @@ export type WebAPIType = {
|
||||||
urgent?: boolean;
|
urgent?: boolean;
|
||||||
}
|
}
|
||||||
) => Promise<MultiRecipient200ResponseType>;
|
) => Promise<MultiRecipient200ResponseType>;
|
||||||
|
setPhoneNumberDiscoverability: (newValue: boolean) => Promise<void>;
|
||||||
setSignedPreKey: (
|
setSignedPreKey: (
|
||||||
signedPreKey: SignedPreKeyType,
|
signedPreKey: SignedPreKeyType,
|
||||||
uuidKind: UUIDKind
|
uuidKind: UUIDKind
|
||||||
|
@ -1272,6 +1277,7 @@ export function initialize({
|
||||||
sendMessages,
|
sendMessages,
|
||||||
sendMessagesUnauth,
|
sendMessagesUnauth,
|
||||||
sendWithSenderKey,
|
sendWithSenderKey,
|
||||||
|
setPhoneNumberDiscoverability,
|
||||||
setSignedPreKey,
|
setSignedPreKey,
|
||||||
startRegistration,
|
startRegistration,
|
||||||
unregisterRequestHandler,
|
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(
|
async function setSignedPreKey(
|
||||||
signedPreKey: SignedPreKeyType,
|
signedPreKey: SignedPreKeyType,
|
||||||
uuidKind: UUIDKind
|
uuidKind: UUIDKind
|
||||||
|
|
|
@ -28,7 +28,7 @@ import { renderClearingDataView } from '../shims/renderClearingDataView';
|
||||||
import * as universalExpireTimer from './universalExpireTimer';
|
import * as universalExpireTimer from './universalExpireTimer';
|
||||||
import { PhoneNumberDiscoverability } from './phoneNumberDiscoverability';
|
import { PhoneNumberDiscoverability } from './phoneNumberDiscoverability';
|
||||||
import { PhoneNumberSharingMode } from './phoneNumberSharingMode';
|
import { PhoneNumberSharingMode } from './phoneNumberSharingMode';
|
||||||
import { assertDev } from './assert';
|
import { strictAssert, assertDev } from './assert';
|
||||||
import * as durations from './durations';
|
import * as durations from './durations';
|
||||||
import type { DurationInSeconds } from './durations';
|
import type { DurationInSeconds } from './durations';
|
||||||
import { isPhoneNumberSharingEnabled } from './isPhoneNumberSharingEnabled';
|
import { isPhoneNumberSharingEnabled } from './isPhoneNumberSharingEnabled';
|
||||||
|
@ -136,8 +136,6 @@ type ValuesWithSetters = Omit<
|
||||||
| 'blockedCount'
|
| 'blockedCount'
|
||||||
| 'defaultConversationColor'
|
| 'defaultConversationColor'
|
||||||
| 'linkPreviewSetting'
|
| 'linkPreviewSetting'
|
||||||
| 'phoneNumberDiscoverabilitySetting'
|
|
||||||
| 'phoneNumberSharingSetting'
|
|
||||||
| 'readReceiptSetting'
|
| 'readReceiptSetting'
|
||||||
| 'typingIndicatorSetting'
|
| 'typingIndicatorSetting'
|
||||||
| 'deviceName'
|
| 'deviceName'
|
||||||
|
@ -177,6 +175,18 @@ export type IPCEventsType = IPCEventsGettersType &
|
||||||
export function createIPCEvents(
|
export function createIPCEvents(
|
||||||
overrideEvents: Partial<IPCEventsType> = {}
|
overrideEvents: Partial<IPCEventsType> = {}
|
||||||
): 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 {
|
return {
|
||||||
getDeviceName: () => window.textsecure.storage.user.getDeviceName(),
|
getDeviceName: () => window.textsecure.storage.user.getDeviceName(),
|
||||||
|
|
||||||
|
@ -185,6 +195,22 @@ export function createIPCEvents(
|
||||||
webFrame.setZoomFactor(zoomFactor);
|
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: () =>
|
getHasStoriesDisabled: () =>
|
||||||
window.storage.get('hasStoriesDisabled', false),
|
window.storage.get('hasStoriesDisabled', false),
|
||||||
setHasStoriesDisabled: async (value: boolean) => {
|
setHasStoriesDisabled: async (value: boolean) => {
|
||||||
|
@ -202,6 +228,8 @@ export function createIPCEvents(
|
||||||
},
|
},
|
||||||
setStoryViewReceiptsEnabled: async (value: boolean) => {
|
setStoryViewReceiptsEnabled: async (value: boolean) => {
|
||||||
await window.storage.put('storyViewReceiptsEnabled', value);
|
await window.storage.put('storyViewReceiptsEnabled', value);
|
||||||
|
const account = window.ConversationController.getOurConversationOrThrow();
|
||||||
|
account.captureChange('storyViewReceiptsEnabled');
|
||||||
},
|
},
|
||||||
|
|
||||||
getPreferredAudioInputDevice: () =>
|
getPreferredAudioInputDevice: () =>
|
||||||
|
|
|
@ -4,5 +4,8 @@
|
||||||
import * as RemoteConfig from '../RemoteConfig';
|
import * as RemoteConfig from '../RemoteConfig';
|
||||||
|
|
||||||
export function isPhoneNumberSharingEnabled(): boolean {
|
export function isPhoneNumberSharingEnabled(): boolean {
|
||||||
return Boolean(RemoteConfig.isEnabled('desktop.internalUser'));
|
return Boolean(
|
||||||
|
RemoteConfig.isEnabled('desktop.internalUser') ||
|
||||||
|
RemoteConfig.isEnabled('desktop.pnp')
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
import type { ConversationAttributesType } from '../model-types.d';
|
import type { ConversationAttributesType } from '../model-types.d';
|
||||||
|
|
||||||
import { makeEnumParser } from './enum';
|
import { makeEnumParser } from './enum';
|
||||||
import { isInSystemContacts } from './isInSystemContacts';
|
|
||||||
import { missingCaseError } from './missingCaseError';
|
import { missingCaseError } from './missingCaseError';
|
||||||
import { isDirectConversation, isMe } from './whatTypeOfConversation';
|
import { isDirectConversation, isMe } from './whatTypeOfConversation';
|
||||||
|
|
||||||
|
@ -35,7 +34,6 @@ export const shouldSharePhoneNumberWith = (
|
||||||
case PhoneNumberSharingMode.Everybody:
|
case PhoneNumberSharingMode.Everybody:
|
||||||
return true;
|
return true;
|
||||||
case PhoneNumberSharingMode.ContactsOnly:
|
case PhoneNumberSharingMode.ContactsOnly:
|
||||||
return isInSystemContacts(conversation);
|
|
||||||
case PhoneNumberSharingMode.Nobody:
|
case PhoneNumberSharingMode.Nobody:
|
||||||
return false;
|
return false;
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -29,7 +29,13 @@ import { createSetting } from '../util/preload';
|
||||||
import { initialize as initializeLogging } from '../logging/set_up_renderer_logging';
|
import { initialize as initializeLogging } from '../logging/set_up_renderer_logging';
|
||||||
import { waitForSettingsChange } from './waitForSettingsChange';
|
import { waitForSettingsChange } from './waitForSettingsChange';
|
||||||
import { createNativeThemeListener } from '../context/createNativeThemeListener';
|
import { createNativeThemeListener } from '../context/createNativeThemeListener';
|
||||||
import { isWindows, isLinux, isMacOS, hasCustomTitleBar } from '../OS';
|
import {
|
||||||
|
isWindows,
|
||||||
|
isLinux,
|
||||||
|
isMacOS,
|
||||||
|
hasCustomTitleBar,
|
||||||
|
getClassName,
|
||||||
|
} from '../OS';
|
||||||
|
|
||||||
const activeWindowService = new ActiveWindowService();
|
const activeWindowService = new ActiveWindowService();
|
||||||
activeWindowService.initialize(window.document, ipcRenderer);
|
activeWindowService.initialize(window.document, ipcRenderer);
|
||||||
|
@ -79,6 +85,7 @@ export type SignalContextType = {
|
||||||
isLinux: typeof isLinux;
|
isLinux: typeof isLinux;
|
||||||
isMacOS: typeof isMacOS;
|
isMacOS: typeof isMacOS;
|
||||||
hasCustomTitleBar: typeof hasCustomTitleBar;
|
hasCustomTitleBar: typeof hasCustomTitleBar;
|
||||||
|
getClassName: typeof getClassName;
|
||||||
};
|
};
|
||||||
config: RendererConfigType;
|
config: RendererConfigType;
|
||||||
getAppInstance: () => string | undefined;
|
getAppInstance: () => string | undefined;
|
||||||
|
@ -108,6 +115,7 @@ export const SignalContext: SignalContextType = {
|
||||||
isLinux,
|
isLinux,
|
||||||
isMacOS,
|
isMacOS,
|
||||||
hasCustomTitleBar,
|
hasCustomTitleBar,
|
||||||
|
getClassName,
|
||||||
},
|
},
|
||||||
bytes: new Bytes(),
|
bytes: new Bytes(),
|
||||||
config,
|
config,
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// 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) {
|
if (window.SignalContext.renderWindow) {
|
||||||
window.SignalContext.renderWindow();
|
window.SignalContext.renderWindow();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -15,6 +15,7 @@ import '../../backbone/reliable_trigger';
|
||||||
|
|
||||||
import type { FeatureFlagType } from '../../window.d';
|
import type { FeatureFlagType } from '../../window.d';
|
||||||
import type { StorageAccessType } from '../../types/Storage.d';
|
import type { StorageAccessType } from '../../types/Storage.d';
|
||||||
|
import type { CdsLookupOptionsType } from '../../textsecure/WebAPI';
|
||||||
import { start as startConversationController } from '../../ConversationController';
|
import { start as startConversationController } from '../../ConversationController';
|
||||||
import { MessageController } from '../../util/MessageController';
|
import { MessageController } from '../../util/MessageController';
|
||||||
import { Environment, getEnvironment } from '../../environment';
|
import { Environment, getEnvironment } from '../../environment';
|
||||||
|
@ -46,6 +47,8 @@ startConversationController();
|
||||||
if (!isProduction(window.SignalContext.getVersion())) {
|
if (!isProduction(window.SignalContext.getVersion())) {
|
||||||
const SignalDebug = {
|
const SignalDebug = {
|
||||||
Data: window.Signal.Data,
|
Data: window.Signal.Data,
|
||||||
|
cdsLookup: (options: CdsLookupOptionsType) =>
|
||||||
|
window.textsecure.server?.cdsLookup(options),
|
||||||
getConversation: (id: string) => window.ConversationController.get(id),
|
getConversation: (id: string) => window.ConversationController.get(id),
|
||||||
getMessageById: (id: string) => window.MessageController.getById(id),
|
getMessageById: (id: string) => window.MessageController.getById(id),
|
||||||
getReduxState: () => window.reduxStore.getState(),
|
getReduxState: () => window.reduxStore.getState(),
|
||||||
|
|
|
@ -24,12 +24,6 @@ installSetting('blockedCount', {
|
||||||
installSetting('linkPreviewSetting', {
|
installSetting('linkPreviewSetting', {
|
||||||
setter: false,
|
setter: false,
|
||||||
});
|
});
|
||||||
installSetting('phoneNumberDiscoverabilitySetting', {
|
|
||||||
setter: false,
|
|
||||||
});
|
|
||||||
installSetting('phoneNumberSharingSetting', {
|
|
||||||
setter: false,
|
|
||||||
});
|
|
||||||
installSetting('readReceiptSetting', {
|
installSetting('readReceiptSetting', {
|
||||||
setter: false,
|
setter: false,
|
||||||
});
|
});
|
||||||
|
@ -63,6 +57,8 @@ installSetting('sentMediaQualitySetting');
|
||||||
installSetting('themeSetting');
|
installSetting('themeSetting');
|
||||||
installSetting('universalExpireTimer');
|
installSetting('universalExpireTimer');
|
||||||
installSetting('zoomFactor');
|
installSetting('zoomFactor');
|
||||||
|
installSetting('phoneNumberDiscoverabilitySetting');
|
||||||
|
installSetting('phoneNumberSharingSetting');
|
||||||
|
|
||||||
// Media Settings
|
// Media Settings
|
||||||
installCallback('getAvailableIODevices');
|
installCallback('getAvailableIODevices');
|
||||||
|
|
|
@ -58,12 +58,9 @@ const settingLinkPreview = createSetting('linkPreviewSetting', {
|
||||||
setter: false,
|
setter: false,
|
||||||
});
|
});
|
||||||
const settingPhoneNumberDiscoverability = createSetting(
|
const settingPhoneNumberDiscoverability = createSetting(
|
||||||
'phoneNumberDiscoverabilitySetting',
|
'phoneNumberDiscoverabilitySetting'
|
||||||
{ setter: false }
|
|
||||||
);
|
);
|
||||||
const settingPhoneNumberSharing = createSetting('phoneNumberSharingSetting', {
|
const settingPhoneNumberSharing = createSetting('phoneNumberSharingSetting');
|
||||||
setter: false,
|
|
||||||
});
|
|
||||||
const settingReadReceipts = createSetting('readReceiptSetting', {
|
const settingReadReceipts = createSetting('readReceiptSetting', {
|
||||||
setter: false,
|
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:
|
// Zoom factor change doesn't require immediate rerender since it will:
|
||||||
// 1. Update the zoom factor in the main window
|
// 1. Update the zoom factor in the main window
|
||||||
// 2. Trigger `preferred-size-changed` in the main process
|
// 2. Trigger `preferred-size-changed` in the main process
|
||||||
|
|
Loading…
Reference in a new issue