Introduce the new Settings tab
Co-authored-by: Jamie Kyle <jamie@signal.org> Co-authored-by: Fedor Indutny <indutny@signal.org> Co-authored-by: Fedor Indutny <79877362+indutny-signal@users.noreply.github.com>
This commit is contained in:
parent
0d906e88ff
commit
fe9d042e40
55 changed files with 1468 additions and 2092 deletions
|
@ -1682,6 +1682,10 @@
|
||||||
"messageformat": "Need help?",
|
"messageformat": "Need help?",
|
||||||
"description": "Shown on the install screen. Link takes users to a support page"
|
"description": "Shown on the install screen. Link takes users to a support page"
|
||||||
},
|
},
|
||||||
|
"icu:Preferences--header": {
|
||||||
|
"messageformat": "Settings",
|
||||||
|
"description": "Shown at the top of the settings tab when open"
|
||||||
|
},
|
||||||
"icu:Preferences--phone-number": {
|
"icu:Preferences--phone-number": {
|
||||||
"messageformat": "Phone Number",
|
"messageformat": "Phone Number",
|
||||||
"description": "The label in settings panel shown for the phone number associated with user's account"
|
"description": "The label in settings panel shown for the phone number associated with user's account"
|
||||||
|
|
82
app/main.ts
82
app/main.ts
|
@ -1173,9 +1173,6 @@ async function readyForUpdates() {
|
||||||
'SettingsChannel must be initialized'
|
'SettingsChannel must be initialized'
|
||||||
);
|
);
|
||||||
await updater.start({
|
await updater.start({
|
||||||
settingsChannel,
|
|
||||||
logger: getLogger(),
|
|
||||||
getMainWindow,
|
|
||||||
canRunSilently: () => {
|
canRunSilently: () => {
|
||||||
return (
|
return (
|
||||||
systemTrayService?.isVisible() === true &&
|
systemTrayService?.isVisible() === true &&
|
||||||
|
@ -1183,6 +1180,9 @@ async function readyForUpdates() {
|
||||||
mainWindow?.webContents?.getBackgroundThrottling() !== false
|
mainWindow?.webContents?.getBackgroundThrottling() !== false
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
getMainWindow,
|
||||||
|
logger: getLogger(),
|
||||||
|
sql,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
getLogger().error(
|
getLogger().error(
|
||||||
|
@ -1424,57 +1424,6 @@ async function showAbout() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let settingsWindow: BrowserWindow | undefined;
|
|
||||||
async function showSettingsWindow() {
|
|
||||||
if (settingsWindow) {
|
|
||||||
settingsWindow.show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
width: 700,
|
|
||||||
height: 700,
|
|
||||||
frame: true,
|
|
||||||
resizable: false,
|
|
||||||
title: getResolvedMessagesLocale().i18n('icu:signalDesktopPreferences'),
|
|
||||||
titleBarStyle: mainTitleBarStyle,
|
|
||||||
autoHideMenuBar: true,
|
|
||||||
backgroundColor: await getBackgroundColor(),
|
|
||||||
show: false,
|
|
||||||
webPreferences: {
|
|
||||||
...defaultWebPrefs,
|
|
||||||
nodeIntegration: false,
|
|
||||||
nodeIntegrationInWorker: false,
|
|
||||||
sandbox: true,
|
|
||||||
contextIsolation: true,
|
|
||||||
preload: join(__dirname, '../bundles/settings/preload.js'),
|
|
||||||
nativeWindowOpen: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
settingsWindow = new BrowserWindow(options);
|
|
||||||
|
|
||||||
await handleCommonWindowEvents(settingsWindow);
|
|
||||||
|
|
||||||
settingsWindow.on('closed', () => {
|
|
||||||
settingsWindow = undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
ipc.once('settings-done-rendering', () => {
|
|
||||||
if (!settingsWindow) {
|
|
||||||
getLogger().warn('settings-done-rendering: no settingsWindow available!');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
settingsWindow.show();
|
|
||||||
});
|
|
||||||
|
|
||||||
await safeLoadURL(
|
|
||||||
settingsWindow,
|
|
||||||
await prepareFileUrl([__dirname, '../settings.html'])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getIsLinked() {
|
async function getIsLinked() {
|
||||||
try {
|
try {
|
||||||
const number = await sql.sqlRead('getItemById', 'number_id');
|
const number = await sql.sqlRead('getItemById', 'number_id');
|
||||||
|
@ -2310,6 +2259,8 @@ app.on('ready', async () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
appStartInitialSpellcheckSetting = await getSpellCheckSetting();
|
||||||
|
|
||||||
// Run window preloading in parallel with database initialization.
|
// Run window preloading in parallel with database initialization.
|
||||||
await createWindow();
|
await createWindow();
|
||||||
|
|
||||||
|
@ -2322,8 +2273,6 @@ app.on('ready', async () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
appStartInitialSpellcheckSetting = await getSpellCheckSetting();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const IDB_KEY = 'indexeddb-delete-needed';
|
const IDB_KEY = 'indexeddb-delete-needed';
|
||||||
const item = await sql.sqlRead('getItemById', IDB_KEY);
|
const item = await sql.sqlRead('getItemById', IDB_KEY);
|
||||||
|
@ -2382,7 +2331,15 @@ function setupMenu(options?: Partial<CreateTemplateOptionsType>) {
|
||||||
showDebugLog: showDebugLogWindow,
|
showDebugLog: showDebugLogWindow,
|
||||||
showCallingDevTools: showCallingDevToolsWindow,
|
showCallingDevTools: showCallingDevToolsWindow,
|
||||||
showKeyboardShortcuts,
|
showKeyboardShortcuts,
|
||||||
showSettings: showSettingsWindow,
|
showSettings: () => {
|
||||||
|
if (!settingsChannel) {
|
||||||
|
getLogger().warn(
|
||||||
|
'showSettings: No settings channel; cannot open settings tab.'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
settingsChannel.openSettingsTab();
|
||||||
|
},
|
||||||
showWindow,
|
showWindow,
|
||||||
zoomIn,
|
zoomIn,
|
||||||
zoomOut,
|
zoomOut,
|
||||||
|
@ -2750,17 +2707,6 @@ function removeDarkOverlay() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ipc.on('show-settings', showSettingsWindow);
|
|
||||||
|
|
||||||
ipc.on('delete-all-data', () => {
|
|
||||||
if (settingsWindow) {
|
|
||||||
settingsWindow.close();
|
|
||||||
}
|
|
||||||
if (mainWindow && mainWindow.webContents) {
|
|
||||||
mainWindow.webContents.send('delete-all-data');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ipc.on('get-config', async event => {
|
ipc.on('get-config', async event => {
|
||||||
const theme = await getResolvedThemeSetting();
|
const theme = await getResolvedThemeSetting();
|
||||||
|
|
||||||
|
|
1
images/icons/v3/settings/settings-fill.svg
Normal file
1
images/icons/v3/settings/settings-fill.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg width="20" height="20" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M8.836 1.284c-.55 0-1.036.36-1.196.887l-.398 1.31a.417.417 0 0 1-.19.24l-1.016.586a.417.417 0 0 1-.302.045L4.4 4.042a1.25 1.25 0 0 0-1.366.592L1.87 6.65a1.25 1.25 0 0 0 .17 1.48l.936 1a.417.417 0 0 1 .112.284v1.172c0 .106-.04.208-.112.285l-.936 1a1.25 1.25 0 0 0-.17 1.48l1.164 2.015c.275.477.83.717 1.366.592l1.334-.31a.416.416 0 0 1 .302.045l1.016.586c.091.053.16.139.19.24l.398 1.31c.16.527.646.887 1.196.887h2.328c.55 0 1.036-.36 1.196-.887l.398-1.31a.417.417 0 0 1 .19-.24l1.016-.586a.417.417 0 0 1 .303-.045l1.333.31a1.25 1.25 0 0 0 1.366-.592l1.164-2.016a1.25 1.25 0 0 0-.17-1.479l-.936-1a.417.417 0 0 1-.112-.285V9.414c0-.106.04-.208.112-.285l.936-1a1.25 1.25 0 0 0 .17-1.48l-1.163-2.015a1.25 1.25 0 0 0-1.366-.592l-1.335.31a.417.417 0 0 1-.302-.045l-1.016-.586a.417.417 0 0 1-.19-.24l-.398-1.31a1.25 1.25 0 0 0-1.196-.887H8.836ZM6.666 10a3.333 3.333 0 1 1 6.667 0 3.333 3.333 0 0 1-6.666 0Z" fill="#000"/></svg>
|
After Width: | Height: | Size: 1 KiB |
|
@ -134,7 +134,6 @@ async function sandboxedEnv() {
|
||||||
path.join(ROOT_DIR, 'ts', 'windows', 'loading', 'start.ts'),
|
path.join(ROOT_DIR, 'ts', 'windows', 'loading', 'start.ts'),
|
||||||
path.join(ROOT_DIR, 'ts', 'windows', 'permissions', 'app.tsx'),
|
path.join(ROOT_DIR, 'ts', 'windows', 'permissions', 'app.tsx'),
|
||||||
path.join(ROOT_DIR, 'ts', 'windows', 'screenShare', 'app.tsx'),
|
path.join(ROOT_DIR, 'ts', 'windows', 'screenShare', 'app.tsx'),
|
||||||
path.join(ROOT_DIR, 'ts', 'windows', 'settings', 'app.tsx'),
|
|
||||||
path.join(
|
path.join(
|
||||||
ROOT_DIR,
|
ROOT_DIR,
|
||||||
'ts',
|
'ts',
|
||||||
|
@ -154,7 +153,6 @@ async function sandboxedEnv() {
|
||||||
path.join(ROOT_DIR, 'ts', 'windows', 'permissions', 'preload.ts'),
|
path.join(ROOT_DIR, 'ts', 'windows', 'permissions', 'preload.ts'),
|
||||||
path.join(ROOT_DIR, 'ts', 'windows', 'calling-tools', 'preload.ts'),
|
path.join(ROOT_DIR, 'ts', 'windows', 'calling-tools', 'preload.ts'),
|
||||||
path.join(ROOT_DIR, 'ts', 'windows', 'screenShare', 'preload.ts'),
|
path.join(ROOT_DIR, 'ts', 'windows', 'screenShare', 'preload.ts'),
|
||||||
path.join(ROOT_DIR, 'ts', 'windows', 'settings', 'preload.ts'),
|
|
||||||
],
|
],
|
||||||
format: 'cjs',
|
format: 'cjs',
|
||||||
outdir: 'bundles',
|
outdir: 'bundles',
|
||||||
|
|
|
@ -170,6 +170,10 @@ $NavTabs__ProfileAvatar__size: 28px;
|
||||||
|
|
||||||
.NavTabs__ItemIcon--Settings {
|
.NavTabs__ItemIcon--Settings {
|
||||||
@include NavTabs__Icon('../images/icons/v3/settings/settings.svg');
|
@include NavTabs__Icon('../images/icons/v3/settings/settings.svg');
|
||||||
|
.NavTabs__Item:active &,
|
||||||
|
.NavTabs__Item[aria-selected='true'] & {
|
||||||
|
@include NavTabs__Icon('../images/icons/v3/settings/settings-fill.svg');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.NavTabs__ItemIcon--Chats {
|
.NavTabs__ItemIcon--Chats {
|
||||||
|
@ -201,13 +205,31 @@ $NavTabs__ProfileAvatar__size: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.NavTabs__TabList {
|
.NavTabs__TabList {
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-direction: column;
|
grid-template-rows:
|
||||||
|
[Chats] auto
|
||||||
|
[Calls] auto
|
||||||
|
[Stories] auto
|
||||||
|
1fr
|
||||||
|
[Settings] auto;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.NavTabs__Item--Chats {
|
||||||
|
grid-row: Chats;
|
||||||
|
}
|
||||||
|
.NavTabs__Item--Calls {
|
||||||
|
grid-row: Calls;
|
||||||
|
}
|
||||||
|
.NavTabs__Item--Stories {
|
||||||
|
grid-row: Stories;
|
||||||
|
}
|
||||||
|
.NavTabs__Item--Settings {
|
||||||
|
grid-row: Settings;
|
||||||
|
}
|
||||||
|
|
||||||
.NavTabs__Misc {
|
.NavTabs__Misc {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -24,6 +24,8 @@ $secondary-text-color: light-dark(
|
||||||
display: flex;
|
display: flex;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
width: 100vw;
|
||||||
|
|
||||||
@include mixins.light-theme {
|
@include mixins.light-theme {
|
||||||
background: variables.$color-white;
|
background: variables.$color-white;
|
||||||
}
|
}
|
||||||
|
@ -31,14 +33,43 @@ $secondary-text-color: light-dark(
|
||||||
background: variables.$color-gray-95;
|
background: variables.$color-gray-95;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
margin-inline-start: 24px;
|
||||||
|
@include mixins.font-title-medium;
|
||||||
|
line-height: 20px;
|
||||||
|
margin-top: 12px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
&__page-selector {
|
&__page-selector {
|
||||||
padding-top: calc(24px + var(--title-bar-drag-area-height));
|
padding-top: var(--title-bar-drag-area-height);
|
||||||
min-width: min(34%, 240px);
|
width: 279px;
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
@include mixins.light-theme {
|
@include mixins.light-theme {
|
||||||
background: variables.$color-gray-02;
|
background: variables.$color-gray-04;
|
||||||
|
border-inline-end: 0.5px solid variables.$color-black-alpha-16;
|
||||||
}
|
}
|
||||||
@include mixins.dark-theme {
|
@include mixins.dark-theme {
|
||||||
background: variables.$color-gray-80;
|
background: variables.$color-gray-80;
|
||||||
|
border-inline-end: 0.5px solid variables.$color-white-alpha-16;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__scroll-area {
|
||||||
|
margin-top: 8px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
max-height: calc(100% - var(--title-bar-drag-area-height) - 20px);
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
@include mixins.light-theme {
|
||||||
|
background: variables.$color-gray-25;
|
||||||
|
border-color: variables.$color-gray-04;
|
||||||
|
}
|
||||||
|
@include mixins.dark-theme {
|
||||||
|
background: variables.$color-gray-45;
|
||||||
|
border-color: variables.$color-gray-80;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,9 +89,12 @@ $secondary-text-color: light-dark(
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
width: 100%;
|
width: calc(100% - 20px);
|
||||||
padding-block: 14px;
|
padding-block: 14px;
|
||||||
padding-inline: 0;
|
padding-inline: 0;
|
||||||
|
margin-inline-start: 10px;
|
||||||
|
margin-inline-end: 10px;
|
||||||
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&--selected {
|
&--selected {
|
||||||
|
@ -130,12 +164,19 @@ $secondary-text-color: light-dark(
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
overflow: overlay;
|
overflow: overlay;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
flex-grow: 1;
|
||||||
|
max-width: 825px;
|
||||||
|
|
||||||
&::-webkit-scrollbar-corner {
|
&::-webkit-scrollbar-corner {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__settings-pane-spacer {
|
||||||
|
flex-grow: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
@include mixins.font-body-1-bold;
|
@include mixins.font-body-1-bold;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
@ -215,6 +215,7 @@ import { waitForEvent } from './shims/events';
|
||||||
import { sendSyncRequests } from './textsecure/syncRequests';
|
import { sendSyncRequests } from './textsecure/syncRequests';
|
||||||
import { handleServerAlerts } from './util/handleServerAlerts';
|
import { handleServerAlerts } from './util/handleServerAlerts';
|
||||||
import { isLocalBackupsEnabled } from './util/isLocalBackupsEnabled';
|
import { isLocalBackupsEnabled } from './util/isLocalBackupsEnabled';
|
||||||
|
import { NavTab } from './state/ducks/nav';
|
||||||
|
|
||||||
export function isOverHourIntoPast(timestamp: number): boolean {
|
export function isOverHourIntoPast(timestamp: number): boolean {
|
||||||
return isNumber(timestamp) && isOlderThan(timestamp, HOUR);
|
return isNumber(timestamp) && isOlderThan(timestamp, HOUR);
|
||||||
|
@ -1356,6 +1357,10 @@ export async function startApp(): Promise<void> {
|
||||||
window.reduxActions.app.openStandalone();
|
window.reduxActions.app.openStandalone();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
window.Whisper.events.on('openSettingsTab', () => {
|
||||||
|
window.reduxActions.nav.changeNavTab(NavTab.Settings);
|
||||||
|
});
|
||||||
|
|
||||||
window.Whisper.events.on('powerMonitorSuspend', () => {
|
window.Whisper.events.on('powerMonitorSuspend', () => {
|
||||||
log.info('powerMonitor: suspend');
|
log.info('powerMonitor: suspend');
|
||||||
server?.cancelInflightRequests('powerMonitorSuspend');
|
server?.cancelInflightRequests('powerMonitorSuspend');
|
||||||
|
|
|
@ -24,7 +24,7 @@ export default {
|
||||||
addCustomColor: action('addCustomColor'),
|
addCustomColor: action('addCustomColor'),
|
||||||
colorSelected: action('colorSelected'),
|
colorSelected: action('colorSelected'),
|
||||||
editCustomColor: action('editCustomColor'),
|
editCustomColor: action('editCustomColor'),
|
||||||
getConversationsWithCustomColor: (_: string) => Promise.resolve([]),
|
getConversationsWithCustomColor: (_: string) => [],
|
||||||
i18n,
|
i18n,
|
||||||
removeCustomColor: action('removeCustomColor'),
|
removeCustomColor: action('removeCustomColor'),
|
||||||
removeCustomColorOnConversations: action(
|
removeCustomColorOnConversations: action(
|
||||||
|
|
|
@ -26,9 +26,7 @@ type CustomColorDataType = {
|
||||||
export type PropsDataType = {
|
export type PropsDataType = {
|
||||||
conversationId?: string;
|
conversationId?: string;
|
||||||
customColors?: Record<string, CustomColorType>;
|
customColors?: Record<string, CustomColorType>;
|
||||||
getConversationsWithCustomColor: (
|
getConversationsWithCustomColor: (colorId: string) => Array<ConversationType>;
|
||||||
colorId: string
|
|
||||||
) => Promise<Array<ConversationType>>;
|
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
isGlobal?: boolean;
|
isGlobal?: boolean;
|
||||||
selectedColor?: ConversationColorType;
|
selectedColor?: ConversationColorType;
|
||||||
|
@ -270,9 +268,7 @@ export function ChatColorPicker({
|
||||||
type CustomColorBubblePropsType = {
|
type CustomColorBubblePropsType = {
|
||||||
color: CustomColorType;
|
color: CustomColorType;
|
||||||
colorId: string;
|
colorId: string;
|
||||||
getConversationsWithCustomColor: (
|
getConversationsWithCustomColor: (colorId: string) => Array<ConversationType>;
|
||||||
colorId: string
|
|
||||||
) => Promise<Array<ConversationType>>;
|
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
onDelete: () => unknown;
|
onDelete: () => unknown;
|
||||||
|
@ -393,12 +389,11 @@ function CustomColorBubble({
|
||||||
attributes={{
|
attributes={{
|
||||||
className: 'ChatColorPicker__context--delete',
|
className: 'ChatColorPicker__context--delete',
|
||||||
}}
|
}}
|
||||||
onClick={async (event: MouseEvent) => {
|
onClick={(event: MouseEvent) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
const conversations =
|
const conversations = getConversationsWithCustomColor(colorId);
|
||||||
await getConversationsWithCustomColor(colorId);
|
|
||||||
if (!conversations.length) {
|
if (!conversations.length) {
|
||||||
onDelete();
|
onDelete();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -3,16 +3,19 @@
|
||||||
|
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
|
|
||||||
import { isBeta } from '../util/version';
|
import { isBeta } from '../util/version';
|
||||||
import { DialogType } from '../types/Dialogs';
|
import { DialogType } from '../types/Dialogs';
|
||||||
import type { LocalizerType } from '../types/Util';
|
|
||||||
import { PRODUCTION_DOWNLOAD_URL, BETA_DOWNLOAD_URL } from '../types/support';
|
import { PRODUCTION_DOWNLOAD_URL, BETA_DOWNLOAD_URL } from '../types/support';
|
||||||
import { I18n } from './I18n';
|
import { I18n } from './I18n';
|
||||||
import { LeftPaneDialog } from './LeftPaneDialog';
|
import { LeftPaneDialog } from './LeftPaneDialog';
|
||||||
import type { WidthBreakpoint } from './_util';
|
|
||||||
import { formatFileSize } from '../util/formatFileSize';
|
import { formatFileSize } from '../util/formatFileSize';
|
||||||
import { getLocalizedUrl } from '../util/getLocalizedUrl';
|
import { getLocalizedUrl } from '../util/getLocalizedUrl';
|
||||||
|
|
||||||
|
import type { LocalizerType } from '../types/Util';
|
||||||
|
import type { DismissOptions } from './LeftPaneDialog';
|
||||||
|
import type { WidthBreakpoint } from './_util';
|
||||||
|
|
||||||
function contactSupportLink(parts: ReactNode): JSX.Element {
|
function contactSupportLink(parts: ReactNode): JSX.Element {
|
||||||
const localizedSupportLink = getLocalizedUrl(
|
const localizedSupportLink = getLocalizedUrl(
|
||||||
'https://support.signal.org/hc/LOCALE/requests/new?desktop'
|
'https://support.signal.org/hc/LOCALE/requests/new?desktop'
|
||||||
|
@ -32,6 +35,7 @@ function contactSupportLink(parts: ReactNode): JSX.Element {
|
||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
containerWidthBreakpoint: WidthBreakpoint;
|
containerWidthBreakpoint: WidthBreakpoint;
|
||||||
dialogType: DialogType;
|
dialogType: DialogType;
|
||||||
|
disableDismiss?: boolean;
|
||||||
dismissDialog: () => void;
|
dismissDialog: () => void;
|
||||||
downloadSize?: number;
|
downloadSize?: number;
|
||||||
downloadedSize?: number;
|
downloadedSize?: number;
|
||||||
|
@ -45,6 +49,7 @@ export type PropsType = {
|
||||||
export function DialogUpdate({
|
export function DialogUpdate({
|
||||||
containerWidthBreakpoint,
|
containerWidthBreakpoint,
|
||||||
dialogType,
|
dialogType,
|
||||||
|
disableDismiss,
|
||||||
dismissDialog,
|
dismissDialog,
|
||||||
downloadSize,
|
downloadSize,
|
||||||
downloadedSize,
|
downloadedSize,
|
||||||
|
@ -215,6 +220,19 @@ export function DialogUpdate({
|
||||||
title = i18n('icu:DialogUpdate__downloaded');
|
title = i18n('icu:DialogUpdate__downloaded');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let dismissOptions: DismissOptions = {
|
||||||
|
hasXButton: true,
|
||||||
|
onClose: snoozeUpdate,
|
||||||
|
closeLabel: i18n('icu:autoUpdateIgnoreButtonLabel'),
|
||||||
|
};
|
||||||
|
if (disableDismiss) {
|
||||||
|
dismissOptions = {
|
||||||
|
hasXButton: false,
|
||||||
|
onClose: undefined,
|
||||||
|
closeLabel: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LeftPaneDialog
|
<LeftPaneDialog
|
||||||
containerWidthBreakpoint={containerWidthBreakpoint}
|
containerWidthBreakpoint={containerWidthBreakpoint}
|
||||||
|
@ -225,9 +243,7 @@ export function DialogUpdate({
|
||||||
hasAction
|
hasAction
|
||||||
onClick={startUpdate}
|
onClick={startUpdate}
|
||||||
clickLabel={clickLabel}
|
clickLabel={clickLabel}
|
||||||
hasXButton
|
{...dismissOptions}
|
||||||
onClose={snoozeUpdate}
|
|
||||||
closeLabel={i18n('icu:autoUpdateIgnoreButtonLabel')}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ export type PropsType = {
|
||||||
renderCustomizingPreferredReactionsModal: () => JSX.Element;
|
renderCustomizingPreferredReactionsModal: () => JSX.Element;
|
||||||
renderNavTabs: (props: SmartNavTabsProps) => JSX.Element;
|
renderNavTabs: (props: SmartNavTabsProps) => JSX.Element;
|
||||||
renderStoriesTab: () => JSX.Element;
|
renderStoriesTab: () => JSX.Element;
|
||||||
|
renderSettingsTab: () => JSX.Element;
|
||||||
};
|
};
|
||||||
|
|
||||||
const PART_COUNT = 16;
|
const PART_COUNT = 16;
|
||||||
|
@ -41,6 +42,7 @@ export function Inbox({
|
||||||
renderCustomizingPreferredReactionsModal,
|
renderCustomizingPreferredReactionsModal,
|
||||||
renderNavTabs,
|
renderNavTabs,
|
||||||
renderStoriesTab,
|
renderStoriesTab,
|
||||||
|
renderSettingsTab,
|
||||||
}: PropsType): JSX.Element {
|
}: PropsType): JSX.Element {
|
||||||
const [internalHasInitialLoadCompleted, setInternalHasInitialLoadCompleted] =
|
const [internalHasInitialLoadCompleted, setInternalHasInitialLoadCompleted] =
|
||||||
useState(hasInitialLoadCompleted);
|
useState(hasInitialLoadCompleted);
|
||||||
|
@ -200,6 +202,7 @@ export function Inbox({
|
||||||
renderChatsTab,
|
renderChatsTab,
|
||||||
renderCallsTab,
|
renderCallsTab,
|
||||||
renderStoriesTab,
|
renderStoriesTab,
|
||||||
|
renderSettingsTab,
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
{activeModal}
|
{activeModal}
|
||||||
|
|
|
@ -9,6 +9,17 @@ import { WidthBreakpoint } from './_util';
|
||||||
|
|
||||||
const BASE_CLASS_NAME = 'LeftPaneDialog';
|
const BASE_CLASS_NAME = 'LeftPaneDialog';
|
||||||
const TOOLTIP_CLASS_NAME = `${BASE_CLASS_NAME}__tooltip`;
|
const TOOLTIP_CLASS_NAME = `${BASE_CLASS_NAME}__tooltip`;
|
||||||
|
export type DismissOptions =
|
||||||
|
| {
|
||||||
|
onClose?: undefined;
|
||||||
|
closeLabel?: undefined;
|
||||||
|
hasXButton?: false;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
onClose: () => void;
|
||||||
|
closeLabel: string;
|
||||||
|
hasXButton: true;
|
||||||
|
};
|
||||||
|
|
||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
type?: 'warning' | 'error' | 'info';
|
type?: 'warning' | 'error' | 'info';
|
||||||
|
@ -30,18 +41,7 @@ export type PropsType = {
|
||||||
hasAction: boolean;
|
hasAction: boolean;
|
||||||
}
|
}
|
||||||
) &
|
) &
|
||||||
(
|
DismissOptions;
|
||||||
| {
|
|
||||||
onClose?: undefined;
|
|
||||||
closeLabel?: undefined;
|
|
||||||
hasXButton?: false;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
onClose: () => void;
|
|
||||||
closeLabel: string;
|
|
||||||
hasXButton: true;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export function LeftPaneDialog({
|
export function LeftPaneDialog({
|
||||||
icon = 'warning',
|
icon = 'warning',
|
||||||
|
|
|
@ -13,7 +13,6 @@ import { NavTab } from '../state/ducks/nav';
|
||||||
import { Tooltip, TooltipPlacement } from './Tooltip';
|
import { Tooltip, TooltipPlacement } from './Tooltip';
|
||||||
import { Theme } from '../util/theme';
|
import { Theme } from '../util/theme';
|
||||||
import type { UnreadStats } from '../util/countUnreadStats';
|
import type { UnreadStats } from '../util/countUnreadStats';
|
||||||
import { ContextMenu } from './ContextMenu';
|
|
||||||
|
|
||||||
type NavTabsItemBadgesProps = Readonly<{
|
type NavTabsItemBadgesProps = Readonly<{
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
|
@ -72,25 +71,33 @@ function NavTabsItemBadges({
|
||||||
}
|
}
|
||||||
|
|
||||||
type NavTabProps = Readonly<{
|
type NavTabProps = Readonly<{
|
||||||
|
hasError?: boolean;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
iconClassName: string;
|
iconClassName: string;
|
||||||
id: NavTab;
|
id: NavTab;
|
||||||
hasError?: boolean;
|
|
||||||
label: string;
|
label: string;
|
||||||
|
navTabClassName: string;
|
||||||
unreadStats: UnreadStats | null;
|
unreadStats: UnreadStats | null;
|
||||||
|
hasPendingUpdate?: boolean;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
function NavTabsItem({
|
function NavTabsItem({
|
||||||
|
hasError,
|
||||||
i18n,
|
i18n,
|
||||||
iconClassName,
|
iconClassName,
|
||||||
id,
|
id,
|
||||||
label,
|
label,
|
||||||
|
navTabClassName,
|
||||||
unreadStats,
|
unreadStats,
|
||||||
hasError,
|
hasPendingUpdate,
|
||||||
}: NavTabProps) {
|
}: NavTabProps) {
|
||||||
const isRTL = i18n.getLocaleDirection() === 'rtl';
|
const isRTL = i18n.getLocaleDirection() === 'rtl';
|
||||||
return (
|
return (
|
||||||
<Tab id={id} data-testid={`NavTabsItem--${id}`} className="NavTabs__Item">
|
<Tab
|
||||||
|
id={id}
|
||||||
|
data-testid={`NavTabsItem--${id}`}
|
||||||
|
className={classNames('NavTabs__Item', navTabClassName)}
|
||||||
|
>
|
||||||
<span className="NavTabs__ItemLabel">{label}</span>
|
<span className="NavTabs__ItemLabel">{label}</span>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content={label}
|
content={label}
|
||||||
|
@ -108,6 +115,7 @@ function NavTabsItem({
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
unreadStats={unreadStats}
|
unreadStats={unreadStats}
|
||||||
hasError={hasError}
|
hasError={hasError}
|
||||||
|
hasPendingUpdate={hasPendingUpdate}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
@ -187,14 +195,13 @@ export type NavTabsProps = Readonly<{
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
me: ConversationType;
|
me: ConversationType;
|
||||||
navTabsCollapsed: boolean;
|
navTabsCollapsed: boolean;
|
||||||
onShowSettings: () => void;
|
|
||||||
onStartUpdate: () => unknown;
|
|
||||||
onNavTabSelected: (tab: NavTab) => void;
|
onNavTabSelected: (tab: NavTab) => void;
|
||||||
onToggleNavTabsCollapse: (collapsed: boolean) => void;
|
onToggleNavTabsCollapse: (collapsed: boolean) => void;
|
||||||
onToggleProfileEditor: () => void;
|
onToggleProfileEditor: () => void;
|
||||||
renderCallsTab: () => ReactNode;
|
renderCallsTab: () => ReactNode;
|
||||||
renderChatsTab: () => ReactNode;
|
renderChatsTab: () => ReactNode;
|
||||||
renderStoriesTab: () => ReactNode;
|
renderStoriesTab: () => ReactNode;
|
||||||
|
renderSettingsTab: () => ReactNode;
|
||||||
selectedNavTab: NavTab;
|
selectedNavTab: NavTab;
|
||||||
storiesEnabled: boolean;
|
storiesEnabled: boolean;
|
||||||
theme: ThemeType;
|
theme: ThemeType;
|
||||||
|
@ -210,14 +217,13 @@ export function NavTabs({
|
||||||
i18n,
|
i18n,
|
||||||
me,
|
me,
|
||||||
navTabsCollapsed,
|
navTabsCollapsed,
|
||||||
onShowSettings,
|
|
||||||
onStartUpdate,
|
|
||||||
onNavTabSelected,
|
onNavTabSelected,
|
||||||
onToggleNavTabsCollapse,
|
onToggleNavTabsCollapse,
|
||||||
onToggleProfileEditor,
|
onToggleProfileEditor,
|
||||||
renderCallsTab,
|
renderCallsTab,
|
||||||
renderChatsTab,
|
renderChatsTab,
|
||||||
renderStoriesTab,
|
renderStoriesTab,
|
||||||
|
renderSettingsTab,
|
||||||
selectedNavTab,
|
selectedNavTab,
|
||||||
storiesEnabled,
|
storiesEnabled,
|
||||||
theme,
|
theme,
|
||||||
|
@ -259,6 +265,7 @@ export function NavTabs({
|
||||||
id={NavTab.Chats}
|
id={NavTab.Chats}
|
||||||
label={i18n('icu:NavTabs__ItemLabel--Chats')}
|
label={i18n('icu:NavTabs__ItemLabel--Chats')}
|
||||||
iconClassName="NavTabs__ItemIcon--Chats"
|
iconClassName="NavTabs__ItemIcon--Chats"
|
||||||
|
navTabClassName="NavTabs__Item--Chats"
|
||||||
unreadStats={unreadConversationsStats}
|
unreadStats={unreadConversationsStats}
|
||||||
/>
|
/>
|
||||||
<NavTabsItem
|
<NavTabsItem
|
||||||
|
@ -266,6 +273,7 @@ export function NavTabs({
|
||||||
id={NavTab.Calls}
|
id={NavTab.Calls}
|
||||||
label={i18n('icu:NavTabs__ItemLabel--Calls')}
|
label={i18n('icu:NavTabs__ItemLabel--Calls')}
|
||||||
iconClassName="NavTabs__ItemIcon--Calls"
|
iconClassName="NavTabs__ItemIcon--Calls"
|
||||||
|
navTabClassName="NavTabs__Item--Calls"
|
||||||
unreadStats={{
|
unreadStats={{
|
||||||
unreadCount: unreadCallsCount,
|
unreadCount: unreadCallsCount,
|
||||||
unreadMentionsCount: 0,
|
unreadMentionsCount: 0,
|
||||||
|
@ -279,6 +287,7 @@ export function NavTabs({
|
||||||
label={i18n('icu:NavTabs__ItemLabel--Stories')}
|
label={i18n('icu:NavTabs__ItemLabel--Stories')}
|
||||||
iconClassName="NavTabs__ItemIcon--Stories"
|
iconClassName="NavTabs__ItemIcon--Stories"
|
||||||
hasError={hasFailedStorySends}
|
hasError={hasFailedStorySends}
|
||||||
|
navTabClassName="NavTabs__Item--Stories"
|
||||||
unreadStats={{
|
unreadStats={{
|
||||||
unreadCount: unreadStoriesCount,
|
unreadCount: unreadStoriesCount,
|
||||||
unreadMentionsCount: 0,
|
unreadMentionsCount: 0,
|
||||||
|
@ -286,75 +295,21 @@ export function NavTabs({
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
<NavTabsItem
|
||||||
|
i18n={i18n}
|
||||||
|
id={NavTab.Settings}
|
||||||
|
label={i18n('icu:NavTabs__ItemLabel--Settings')}
|
||||||
|
iconClassName="NavTabs__ItemIcon--Settings"
|
||||||
|
navTabClassName="NavTabs__Item--Settings"
|
||||||
|
unreadStats={{
|
||||||
|
unreadCount: unreadCallsCount,
|
||||||
|
unreadMentionsCount: 0,
|
||||||
|
markedUnread: false,
|
||||||
|
}}
|
||||||
|
hasPendingUpdate={hasPendingUpdate}
|
||||||
|
/>
|
||||||
</TabList>
|
</TabList>
|
||||||
<div className="NavTabs__Misc">
|
<div className="NavTabs__Misc">
|
||||||
<ContextMenu
|
|
||||||
i18n={i18n}
|
|
||||||
menuOptions={[
|
|
||||||
{
|
|
||||||
icon: 'NavTabs__ContextMenuIcon--Settings',
|
|
||||||
label: i18n('icu:NavTabs__ItemLabel--Settings'),
|
|
||||||
onClick: onShowSettings,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'NavTabs__ContextMenuIcon--Update',
|
|
||||||
label: i18n('icu:NavTabs__ItemLabel--Update'),
|
|
||||||
onClick: onStartUpdate,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
popperOptions={{
|
|
||||||
placement: 'top-start',
|
|
||||||
strategy: 'absolute',
|
|
||||||
}}
|
|
||||||
portalToRoot
|
|
||||||
>
|
|
||||||
{({ onClick, onKeyDown, ref }) => {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="NavTabs__Item"
|
|
||||||
onKeyDown={event => {
|
|
||||||
if (hasPendingUpdate) {
|
|
||||||
onKeyDown(event);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onClick={event => {
|
|
||||||
if (hasPendingUpdate) {
|
|
||||||
onClick(event);
|
|
||||||
} else {
|
|
||||||
onShowSettings();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Tooltip
|
|
||||||
content={i18n('icu:NavTabs__ItemLabel--Settings')}
|
|
||||||
theme={Theme.Dark}
|
|
||||||
direction={TooltipPlacement.Right}
|
|
||||||
delay={600}
|
|
||||||
>
|
|
||||||
<span className="NavTabs__ItemButton" ref={ref}>
|
|
||||||
<span className="NavTabs__ItemContent">
|
|
||||||
<span
|
|
||||||
role="presentation"
|
|
||||||
className="NavTabs__ItemIcon NavTabs__ItemIcon--Settings"
|
|
||||||
/>
|
|
||||||
<span className="NavTabs__ItemLabel">
|
|
||||||
{i18n('icu:NavTabs__ItemLabel--Settings')}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<NavTabsItemBadges
|
|
||||||
i18n={i18n}
|
|
||||||
unreadStats={null}
|
|
||||||
hasPendingUpdate={hasPendingUpdate}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</Tooltip>
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</ContextMenu>
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="NavTabs__Item NavTabs__Item--Profile"
|
className="NavTabs__Item NavTabs__Item--Profile"
|
||||||
|
@ -402,6 +357,9 @@ export function NavTabs({
|
||||||
<TabPanel id={NavTab.Stories} className="NavTabs__TabPanel">
|
<TabPanel id={NavTab.Stories} className="NavTabs__TabPanel">
|
||||||
{renderStoriesTab}
|
{renderStoriesTab}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
<TabPanel id={NavTab.Settings} className="NavTabs__TabPanel">
|
||||||
|
{renderSettingsTab}
|
||||||
|
</TabPanel>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,13 +5,17 @@ import type { Meta, StoryFn } from '@storybook/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from '@storybook/addon-actions';
|
||||||
import type { PropsType } from './Preferences';
|
|
||||||
import { Page, Preferences } from './Preferences';
|
import { Page, Preferences } from './Preferences';
|
||||||
import { DEFAULT_CONVERSATION_COLOR } from '../types/Colors';
|
import { DEFAULT_CONVERSATION_COLOR } from '../types/Colors';
|
||||||
import { PhoneNumberSharingMode } from '../util/phoneNumberSharingMode';
|
import { PhoneNumberSharingMode } from '../util/phoneNumberSharingMode';
|
||||||
import { PhoneNumberDiscoverability } from '../util/phoneNumberDiscoverability';
|
import { PhoneNumberDiscoverability } from '../util/phoneNumberDiscoverability';
|
||||||
import { EmojiSkinTone } from './fun/data/emojis';
|
import { EmojiSkinTone } from './fun/data/emojis';
|
||||||
import { DAY, DurationInSeconds, WEEK } from '../util/durations';
|
import { DAY, DurationInSeconds, WEEK } from '../util/durations';
|
||||||
|
import { DialogUpdate } from './DialogUpdate';
|
||||||
|
import { DialogType } from '../types/Dialogs';
|
||||||
|
|
||||||
|
import type { PropsType } from './Preferences';
|
||||||
|
import type { WidthBreakpoint } from './_util';
|
||||||
|
|
||||||
const { i18n } = window.SignalContext;
|
const { i18n } = window.SignalContext;
|
||||||
|
|
||||||
|
@ -65,6 +69,26 @@ const exportLocalBackupResult = {
|
||||||
snapshotDir: '/home/signaluser/SignalBackups/signal-backup-1745618069169',
|
snapshotDir: '/home/signaluser/SignalBackups/signal-backup-1745618069169',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function renderUpdateDialog(
|
||||||
|
props: Readonly<{ containerWidthBreakpoint: WidthBreakpoint }>
|
||||||
|
): JSX.Element {
|
||||||
|
return (
|
||||||
|
<DialogUpdate
|
||||||
|
i18n={i18n}
|
||||||
|
containerWidthBreakpoint={props.containerWidthBreakpoint}
|
||||||
|
dialogType={DialogType.DownloadReady}
|
||||||
|
downloadSize={100000}
|
||||||
|
downloadedSize={50000}
|
||||||
|
version="8.99.0"
|
||||||
|
currentVersion="8.98.00"
|
||||||
|
disableDismiss
|
||||||
|
dismissDialog={action('dismissDialog')}
|
||||||
|
snoozeUpdate={action('snoozeUpdate')}
|
||||||
|
startUpdate={action('startUpdate')}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Components/Preferences',
|
title: 'Components/Preferences',
|
||||||
component: Preferences,
|
component: Preferences,
|
||||||
|
@ -123,6 +147,7 @@ export default {
|
||||||
hasMinimizeToSystemTray: true,
|
hasMinimizeToSystemTray: true,
|
||||||
hasNotificationAttention: false,
|
hasNotificationAttention: false,
|
||||||
hasNotifications: true,
|
hasNotifications: true,
|
||||||
|
hasPendingUpdate: false,
|
||||||
hasReadReceipts: true,
|
hasReadReceipts: true,
|
||||||
hasRelayCalls: false,
|
hasRelayCalls: false,
|
||||||
hasSpellCheck: true,
|
hasSpellCheck: true,
|
||||||
|
@ -140,6 +165,7 @@ export default {
|
||||||
isContentProtectionSupported: true,
|
isContentProtectionSupported: true,
|
||||||
isContentProtectionNeeded: true,
|
isContentProtectionNeeded: true,
|
||||||
isMinimizeToAndStartInSystemTraySupported: true,
|
isMinimizeToAndStartInSystemTraySupported: true,
|
||||||
|
isUpdateDownloaded: false,
|
||||||
lastSyncTime: Date.now(),
|
lastSyncTime: Date.now(),
|
||||||
localeOverride: null,
|
localeOverride: null,
|
||||||
notificationContent: 'name',
|
notificationContent: 'name',
|
||||||
|
@ -156,12 +182,11 @@ export default {
|
||||||
whoCanSeeMe: PhoneNumberSharingMode.Everybody,
|
whoCanSeeMe: PhoneNumberSharingMode.Everybody,
|
||||||
zoomFactor: 1,
|
zoomFactor: 1,
|
||||||
|
|
||||||
getConversationsWithCustomColor: () => Promise.resolve([]),
|
renderUpdateDialog,
|
||||||
|
getConversationsWithCustomColor: () => [],
|
||||||
|
|
||||||
addCustomColor: action('addCustomColor'),
|
addCustomColor: action('addCustomColor'),
|
||||||
closeSettings: action('closeSettings'),
|
|
||||||
doDeleteAllData: action('doDeleteAllData'),
|
doDeleteAllData: action('doDeleteAllData'),
|
||||||
doneRendering: action('doneRendering'),
|
|
||||||
editCustomColor: action('editCustomColor'),
|
editCustomColor: action('editCustomColor'),
|
||||||
exportLocalBackup: async () => {
|
exportLocalBackup: async () => {
|
||||||
return {
|
return {
|
||||||
|
@ -211,6 +236,7 @@ export default {
|
||||||
onSelectedSpeakerChange: action('onSelectedSpeakerChange'),
|
onSelectedSpeakerChange: action('onSelectedSpeakerChange'),
|
||||||
onSentMediaQualityChange: action('onSentMediaQualityChange'),
|
onSentMediaQualityChange: action('onSentMediaQualityChange'),
|
||||||
onSpellCheckChange: action('onSpellCheckChange'),
|
onSpellCheckChange: action('onSpellCheckChange'),
|
||||||
|
onStartUpdate: action('onStartUpdate'),
|
||||||
onTextFormattingChange: action('onTextFormattingChange'),
|
onTextFormattingChange: action('onTextFormattingChange'),
|
||||||
onThemeChange: action('onThemeChange'),
|
onThemeChange: action('onThemeChange'),
|
||||||
onUniversalExpireTimerChange: action('onUniversalExpireTimerChange'),
|
onUniversalExpireTimerChange: action('onUniversalExpireTimerChange'),
|
||||||
|
@ -350,3 +376,13 @@ Internal.args = {
|
||||||
initialPage: Page.Internal,
|
initialPage: Page.Internal,
|
||||||
isInternalUser: true,
|
isInternalUser: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const UpdateAvailable = Template.bind({});
|
||||||
|
UpdateAvailable.args = {
|
||||||
|
hasPendingUpdate: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const UpdateDownloaded = Template.bind({});
|
||||||
|
UpdateDownloaded.args = {
|
||||||
|
isUpdateDownloaded: true,
|
||||||
|
};
|
||||||
|
|
|
@ -9,7 +9,7 @@ import React, {
|
||||||
useState,
|
useState,
|
||||||
useId,
|
useId,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { noop, partition } from 'lodash';
|
import { isNumber, noop, partition } from 'lodash';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import * as LocaleMatcher from '@formatjs/intl-localematcher';
|
import * as LocaleMatcher from '@formatjs/intl-localematcher';
|
||||||
import type { MediaDeviceSettings } from '../types/Calling';
|
import type { MediaDeviceSettings } from '../types/Calling';
|
||||||
|
@ -53,7 +53,6 @@ import {
|
||||||
format as formatExpirationTimer,
|
format as formatExpirationTimer,
|
||||||
} from '../util/expirationTimer';
|
} from '../util/expirationTimer';
|
||||||
import { DurationInSeconds } from '../util/durations';
|
import { DurationInSeconds } from '../util/durations';
|
||||||
import { useEscapeHandling } from '../hooks/useEscapeHandling';
|
|
||||||
import { focusableSelector } from '../util/focusableSelectors';
|
import { focusableSelector } from '../util/focusableSelectors';
|
||||||
import { Modal } from './Modal';
|
import { Modal } from './Modal';
|
||||||
import { SearchInput } from './SearchInput';
|
import { SearchInput } from './SearchInput';
|
||||||
|
@ -93,24 +92,24 @@ export type PropsDataType = {
|
||||||
hasAudioNotifications?: boolean;
|
hasAudioNotifications?: boolean;
|
||||||
hasAutoConvertEmoji: boolean;
|
hasAutoConvertEmoji: boolean;
|
||||||
hasAutoDownloadUpdate: boolean;
|
hasAutoDownloadUpdate: boolean;
|
||||||
hasAutoLaunch: boolean;
|
hasAutoLaunch: boolean | undefined;
|
||||||
hasCallNotifications: boolean;
|
hasCallNotifications: boolean;
|
||||||
hasCallRingtoneNotification: boolean;
|
hasCallRingtoneNotification: boolean;
|
||||||
hasContentProtection: boolean;
|
hasContentProtection: boolean | undefined;
|
||||||
hasCountMutedConversations: boolean;
|
hasCountMutedConversations: boolean;
|
||||||
hasHideMenuBar?: boolean;
|
hasHideMenuBar?: boolean;
|
||||||
hasIncomingCallNotifications: boolean;
|
hasIncomingCallNotifications: boolean;
|
||||||
hasLinkPreviews: boolean;
|
hasLinkPreviews: boolean;
|
||||||
hasMediaCameraPermissions: boolean | undefined;
|
hasMediaCameraPermissions: boolean | undefined;
|
||||||
hasMediaPermissions: boolean;
|
hasMediaPermissions: boolean | undefined;
|
||||||
hasMessageAudio: boolean;
|
hasMessageAudio: boolean;
|
||||||
hasMinimizeToAndStartInSystemTray: boolean;
|
hasMinimizeToAndStartInSystemTray: boolean | undefined;
|
||||||
hasMinimizeToSystemTray: boolean;
|
hasMinimizeToSystemTray: boolean | undefined;
|
||||||
hasNotificationAttention: boolean;
|
hasNotificationAttention: boolean;
|
||||||
hasNotifications: boolean;
|
hasNotifications: boolean;
|
||||||
hasReadReceipts: boolean;
|
hasReadReceipts: boolean;
|
||||||
hasRelayCalls?: boolean;
|
hasRelayCalls?: boolean;
|
||||||
hasSpellCheck: boolean;
|
hasSpellCheck: boolean | undefined;
|
||||||
hasStoriesDisabled: boolean;
|
hasStoriesDisabled: boolean;
|
||||||
hasTextFormatting: boolean;
|
hasTextFormatting: boolean;
|
||||||
hasTypingIndicators: boolean;
|
hasTypingIndicators: boolean;
|
||||||
|
@ -122,51 +121,56 @@ export type PropsDataType = {
|
||||||
selectedMicrophone?: AudioDevice;
|
selectedMicrophone?: AudioDevice;
|
||||||
selectedSpeaker?: AudioDevice;
|
selectedSpeaker?: AudioDevice;
|
||||||
sentMediaQualitySetting: SentMediaQualitySettingType;
|
sentMediaQualitySetting: SentMediaQualitySettingType;
|
||||||
themeSetting: ThemeSettingType;
|
themeSetting: ThemeSettingType | undefined;
|
||||||
universalExpireTimer: DurationInSeconds;
|
universalExpireTimer: DurationInSeconds;
|
||||||
whoCanFindMe: PhoneNumberDiscoverability;
|
whoCanFindMe: PhoneNumberDiscoverability;
|
||||||
whoCanSeeMe: PhoneNumberSharingMode;
|
whoCanSeeMe: PhoneNumberSharingMode;
|
||||||
zoomFactor: ZoomFactorType;
|
zoomFactor: ZoomFactorType | undefined;
|
||||||
|
|
||||||
// Localization
|
// Localization
|
||||||
availableLocales: ReadonlyArray<string>;
|
availableLocales: ReadonlyArray<string>;
|
||||||
localeOverride: string | null;
|
localeOverride: string | null | undefined;
|
||||||
preferredSystemLocales: ReadonlyArray<string>;
|
preferredSystemLocales: ReadonlyArray<string>;
|
||||||
resolvedLocale: string;
|
resolvedLocale: string;
|
||||||
|
|
||||||
// Other props
|
// Other props
|
||||||
|
hasPendingUpdate: boolean;
|
||||||
initialSpellCheckSetting: boolean;
|
initialSpellCheckSetting: boolean;
|
||||||
|
isUpdateDownloaded: boolean;
|
||||||
|
|
||||||
// Limited support features
|
// Limited support features
|
||||||
isAutoDownloadUpdatesSupported: boolean;
|
isAutoDownloadUpdatesSupported: boolean;
|
||||||
isAutoLaunchSupported: boolean;
|
isAutoLaunchSupported: boolean;
|
||||||
|
isContentProtectionNeeded: boolean;
|
||||||
|
isContentProtectionSupported: boolean;
|
||||||
isHideMenuBarSupported: boolean;
|
isHideMenuBarSupported: boolean;
|
||||||
isNotificationAttentionSupported: boolean;
|
isNotificationAttentionSupported: boolean;
|
||||||
isSyncSupported: boolean;
|
isSyncSupported: boolean;
|
||||||
isSystemTraySupported: boolean;
|
isSystemTraySupported: boolean;
|
||||||
isMinimizeToAndStartInSystemTraySupported: boolean;
|
isMinimizeToAndStartInSystemTraySupported: boolean;
|
||||||
isInternalUser: boolean;
|
isInternalUser: boolean;
|
||||||
isContentProtectionNeeded: boolean;
|
|
||||||
isContentProtectionSupported: boolean;
|
|
||||||
|
|
||||||
|
// Devices
|
||||||
availableCameras: Array<
|
availableCameras: Array<
|
||||||
Pick<MediaDeviceInfo, 'deviceId' | 'groupId' | 'kind' | 'label'>
|
Pick<MediaDeviceInfo, 'deviceId' | 'groupId' | 'kind' | 'label'>
|
||||||
>;
|
>;
|
||||||
} & Omit<MediaDeviceSettings, 'availableCameras'>;
|
} & Omit<MediaDeviceSettings, 'availableCameras'>;
|
||||||
|
|
||||||
type PropsFunctionType = {
|
type PropsFunctionType = {
|
||||||
|
// Render props
|
||||||
|
renderUpdateDialog: (
|
||||||
|
_: Readonly<{ containerWidthBreakpoint: WidthBreakpoint }>
|
||||||
|
) => JSX.Element;
|
||||||
|
|
||||||
// Other props
|
// Other props
|
||||||
addCustomColor: (color: CustomColorType) => unknown;
|
addCustomColor: (color: CustomColorType) => unknown;
|
||||||
closeSettings: () => unknown;
|
|
||||||
doDeleteAllData: () => unknown;
|
doDeleteAllData: () => unknown;
|
||||||
doneRendering: () => unknown;
|
|
||||||
editCustomColor: (colorId: string, color: CustomColorType) => unknown;
|
editCustomColor: (colorId: string, color: CustomColorType) => unknown;
|
||||||
exportLocalBackup: () => Promise<BackupValidationResultType>;
|
exportLocalBackup: () => Promise<BackupValidationResultType>;
|
||||||
getConversationsWithCustomColor: (
|
getConversationsWithCustomColor: (colorId: string) => Array<ConversationType>;
|
||||||
colorId: string
|
|
||||||
) => Promise<Array<ConversationType>>;
|
|
||||||
importLocalBackup: () => Promise<ValidateLocalBackupStructureResultType>;
|
importLocalBackup: () => Promise<ValidateLocalBackupStructureResultType>;
|
||||||
makeSyncRequest: () => unknown;
|
makeSyncRequest: () => unknown;
|
||||||
|
onStartUpdate: () => unknown;
|
||||||
refreshCloudBackupStatus: () => void;
|
refreshCloudBackupStatus: () => void;
|
||||||
refreshBackupSubscriptionStatus: () => void;
|
refreshBackupSubscriptionStatus: () => void;
|
||||||
removeCustomColor: (colorId: string) => unknown;
|
removeCustomColor: (colorId: string) => unknown;
|
||||||
|
@ -199,7 +203,7 @@ type PropsFunctionType = {
|
||||||
onHideMenuBarChange: CheckboxChangeHandlerType;
|
onHideMenuBarChange: CheckboxChangeHandlerType;
|
||||||
onIncomingCallNotificationsChange: CheckboxChangeHandlerType;
|
onIncomingCallNotificationsChange: CheckboxChangeHandlerType;
|
||||||
onLastSyncTimeChange: (time: number) => unknown;
|
onLastSyncTimeChange: (time: number) => unknown;
|
||||||
onLocaleChange: (locale: string | null) => void;
|
onLocaleChange: (locale: string | null | undefined) => void;
|
||||||
onMediaCameraPermissionsChange: CheckboxChangeHandlerType;
|
onMediaCameraPermissionsChange: CheckboxChangeHandlerType;
|
||||||
onMediaPermissionsChange: CheckboxChangeHandlerType;
|
onMediaPermissionsChange: CheckboxChangeHandlerType;
|
||||||
onMessageAudioChange: CheckboxChangeHandlerType;
|
onMessageAudioChange: CheckboxChangeHandlerType;
|
||||||
|
@ -284,13 +288,11 @@ export function Preferences({
|
||||||
backupFeatureEnabled,
|
backupFeatureEnabled,
|
||||||
backupSubscriptionStatus,
|
backupSubscriptionStatus,
|
||||||
blockedCount,
|
blockedCount,
|
||||||
closeSettings,
|
|
||||||
cloudBackupStatus,
|
cloudBackupStatus,
|
||||||
customColors,
|
customColors,
|
||||||
defaultConversationColor,
|
defaultConversationColor,
|
||||||
deviceName = '',
|
deviceName = '',
|
||||||
doDeleteAllData,
|
doDeleteAllData,
|
||||||
doneRendering,
|
|
||||||
editCustomColor,
|
editCustomColor,
|
||||||
emojiSkinToneDefault,
|
emojiSkinToneDefault,
|
||||||
exportLocalBackup,
|
exportLocalBackup,
|
||||||
|
@ -313,6 +315,7 @@ export function Preferences({
|
||||||
hasMinimizeToSystemTray,
|
hasMinimizeToSystemTray,
|
||||||
hasNotificationAttention,
|
hasNotificationAttention,
|
||||||
hasNotifications,
|
hasNotifications,
|
||||||
|
hasPendingUpdate,
|
||||||
hasReadReceipts,
|
hasReadReceipts,
|
||||||
hasRelayCalls,
|
hasRelayCalls,
|
||||||
hasSpellCheck,
|
hasSpellCheck,
|
||||||
|
@ -325,14 +328,15 @@ export function Preferences({
|
||||||
initialSpellCheckSetting,
|
initialSpellCheckSetting,
|
||||||
isAutoDownloadUpdatesSupported,
|
isAutoDownloadUpdatesSupported,
|
||||||
isAutoLaunchSupported,
|
isAutoLaunchSupported,
|
||||||
|
isContentProtectionNeeded,
|
||||||
|
isContentProtectionSupported,
|
||||||
isHideMenuBarSupported,
|
isHideMenuBarSupported,
|
||||||
isNotificationAttentionSupported,
|
isNotificationAttentionSupported,
|
||||||
isSyncSupported,
|
isSyncSupported,
|
||||||
isSystemTraySupported,
|
isSystemTraySupported,
|
||||||
isMinimizeToAndStartInSystemTraySupported,
|
isMinimizeToAndStartInSystemTraySupported,
|
||||||
isInternalUser,
|
isInternalUser,
|
||||||
isContentProtectionNeeded,
|
isUpdateDownloaded,
|
||||||
isContentProtectionSupported,
|
|
||||||
lastSyncTime,
|
lastSyncTime,
|
||||||
makeSyncRequest,
|
makeSyncRequest,
|
||||||
notificationContent,
|
notificationContent,
|
||||||
|
@ -377,6 +381,7 @@ export function Preferences({
|
||||||
refreshBackupSubscriptionStatus,
|
refreshBackupSubscriptionStatus,
|
||||||
removeCustomColor,
|
removeCustomColor,
|
||||||
removeCustomColorOnConversations,
|
removeCustomColorOnConversations,
|
||||||
|
renderUpdateDialog,
|
||||||
resetAllChatColors,
|
resetAllChatColors,
|
||||||
resetDefaultChatColor,
|
resetDefaultChatColor,
|
||||||
resolvedLocale,
|
resolvedLocale,
|
||||||
|
@ -411,7 +416,7 @@ export function Preferences({
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
const [selectedLanguageLocale, setSelectedLanguageLocale] = useState<
|
const [selectedLanguageLocale, setSelectedLanguageLocale] = useState<
|
||||||
string | null
|
string | null | undefined
|
||||||
>(localeOverride);
|
>(localeOverride);
|
||||||
const [languageSearchInput, setLanguageSearchInput] = useState('');
|
const [languageSearchInput, setLanguageSearchInput] = useState('');
|
||||||
const [toast, setToast] = useState<AnyToast | undefined>();
|
const [toast, setToast] = useState<AnyToast | undefined>();
|
||||||
|
@ -432,6 +437,13 @@ export function Preferences({
|
||||||
setPage(Page.General);
|
setPage(Page.General);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let maybeUpdateDialog: JSX.Element | undefined;
|
||||||
|
if (hasPendingUpdate || isUpdateDownloaded) {
|
||||||
|
maybeUpdateDialog = renderUpdateDialog({
|
||||||
|
containerWidthBreakpoint: WidthBreakpoint.Wide,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (page === Page.Backups) {
|
if (page === Page.Backups) {
|
||||||
refreshCloudBackupStatus();
|
refreshCloudBackupStatus();
|
||||||
|
@ -439,18 +451,6 @@ export function Preferences({
|
||||||
}
|
}
|
||||||
}, [page, refreshCloudBackupStatus, refreshBackupSubscriptionStatus]);
|
}, [page, refreshCloudBackupStatus, refreshBackupSubscriptionStatus]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
doneRendering();
|
|
||||||
}, [doneRendering]);
|
|
||||||
|
|
||||||
useEscapeHandling(() => {
|
|
||||||
if (languageDialog != null) {
|
|
||||||
closeLanguageDialog();
|
|
||||||
} else {
|
|
||||||
closeSettings();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const onZoomSelectChange = useCallback(
|
const onZoomSelectChange = useCallback(
|
||||||
(value: string) => {
|
(value: string) => {
|
||||||
const number = parseFloat(value);
|
const number = parseFloat(value);
|
||||||
|
@ -631,6 +631,7 @@ export function Preferences({
|
||||||
{isAutoLaunchSupported && (
|
{isAutoLaunchSupported && (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={hasAutoLaunch}
|
checked={hasAutoLaunch}
|
||||||
|
disabled={hasAutoLaunch === undefined}
|
||||||
label={i18n('icu:autoLaunchDescription')}
|
label={i18n('icu:autoLaunchDescription')}
|
||||||
moduleClassName="Preferences__checkbox"
|
moduleClassName="Preferences__checkbox"
|
||||||
name="autoLaunch"
|
name="autoLaunch"
|
||||||
|
@ -650,6 +651,7 @@ export function Preferences({
|
||||||
<>
|
<>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={hasMinimizeToSystemTray}
|
checked={hasMinimizeToSystemTray}
|
||||||
|
disabled={hasMinimizeToSystemTray === undefined}
|
||||||
label={i18n('icu:SystemTraySetting__minimize-to-system-tray')}
|
label={i18n('icu:SystemTraySetting__minimize-to-system-tray')}
|
||||||
moduleClassName="Preferences__checkbox"
|
moduleClassName="Preferences__checkbox"
|
||||||
name="system-tray-setting-minimize-to-system-tray"
|
name="system-tray-setting-minimize-to-system-tray"
|
||||||
|
@ -658,7 +660,10 @@ export function Preferences({
|
||||||
{isMinimizeToAndStartInSystemTraySupported && (
|
{isMinimizeToAndStartInSystemTraySupported && (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={hasMinimizeToAndStartInSystemTray}
|
checked={hasMinimizeToAndStartInSystemTray}
|
||||||
disabled={!hasMinimizeToSystemTray}
|
disabled={
|
||||||
|
!hasMinimizeToSystemTray ||
|
||||||
|
hasMinimizeToAndStartInSystemTray === undefined
|
||||||
|
}
|
||||||
label={i18n(
|
label={i18n(
|
||||||
'icu:SystemTraySetting__minimize-to-and-start-in-system-tray'
|
'icu:SystemTraySetting__minimize-to-and-start-in-system-tray'
|
||||||
)}
|
)}
|
||||||
|
@ -673,6 +678,7 @@ export function Preferences({
|
||||||
<SettingsRow title={i18n('icu:permissions')}>
|
<SettingsRow title={i18n('icu:permissions')}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={hasMediaPermissions}
|
checked={hasMediaPermissions}
|
||||||
|
disabled={hasMediaPermissions === undefined}
|
||||||
label={i18n('icu:mediaPermissionsDescription')}
|
label={i18n('icu:mediaPermissionsDescription')}
|
||||||
moduleClassName="Preferences__checkbox"
|
moduleClassName="Preferences__checkbox"
|
||||||
name="mediaPermissions"
|
name="mediaPermissions"
|
||||||
|
@ -680,6 +686,7 @@ export function Preferences({
|
||||||
/>
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={hasMediaCameraPermissions ?? false}
|
checked={hasMediaCameraPermissions ?? false}
|
||||||
|
disabled={hasMediaCameraPermissions === undefined}
|
||||||
label={i18n('icu:mediaCameraPermissionsDescription')}
|
label={i18n('icu:mediaCameraPermissionsDescription')}
|
||||||
moduleClassName="Preferences__checkbox"
|
moduleClassName="Preferences__checkbox"
|
||||||
name="mediaCameraPermissions"
|
name="mediaCameraPermissions"
|
||||||
|
@ -702,7 +709,10 @@ export function Preferences({
|
||||||
} else if (page === Page.Appearance) {
|
} else if (page === Page.Appearance) {
|
||||||
let zoomFactors = DEFAULT_ZOOM_FACTORS;
|
let zoomFactors = DEFAULT_ZOOM_FACTORS;
|
||||||
|
|
||||||
if (!zoomFactors.some(({ value }) => value === zoomFactor)) {
|
if (
|
||||||
|
isNumber(zoomFactor) &&
|
||||||
|
!zoomFactors.some(({ value }) => value === zoomFactor)
|
||||||
|
) {
|
||||||
zoomFactors = [
|
zoomFactors = [
|
||||||
...zoomFactors,
|
...zoomFactors,
|
||||||
{
|
{
|
||||||
|
@ -711,6 +721,13 @@ export function Preferences({
|
||||||
},
|
},
|
||||||
].sort((a, b) => a.value - b.value);
|
].sort((a, b) => a.value - b.value);
|
||||||
}
|
}
|
||||||
|
let localeText = '';
|
||||||
|
if (localeOverride !== undefined) {
|
||||||
|
localeText =
|
||||||
|
localeOverride != null
|
||||||
|
? getLocaleDisplayName(resolvedLocale, localeOverride)
|
||||||
|
: i18n('icu:Preferences__Language__SystemLanguage');
|
||||||
|
}
|
||||||
|
|
||||||
settings = (
|
settings = (
|
||||||
<>
|
<>
|
||||||
|
@ -728,12 +745,14 @@ export function Preferences({
|
||||||
className="Preferences__LanguageButton"
|
className="Preferences__LanguageButton"
|
||||||
lang={localeOverride ?? resolvedLocale}
|
lang={localeOverride ?? resolvedLocale}
|
||||||
>
|
>
|
||||||
{localeOverride != null
|
{localeText}
|
||||||
? getLocaleDisplayName(resolvedLocale, localeOverride)
|
|
||||||
: i18n('icu:Preferences__Language__SystemLanguage')}
|
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
// We haven't loaded the user's setting yet
|
||||||
|
if (localeOverride === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
setLanguageDialog(LanguageDialog.Selection);
|
setLanguageDialog(LanguageDialog.Selection);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -852,6 +871,7 @@ export function Preferences({
|
||||||
right={
|
right={
|
||||||
<Select
|
<Select
|
||||||
id={themeSelectId}
|
id={themeSelectId}
|
||||||
|
disabled={themeSetting === undefined}
|
||||||
onChange={onThemeChange}
|
onChange={onThemeChange}
|
||||||
options={[
|
options={[
|
||||||
{
|
{
|
||||||
|
@ -898,8 +918,9 @@ export function Preferences({
|
||||||
right={
|
right={
|
||||||
<Select
|
<Select
|
||||||
id={zoomSelectId}
|
id={zoomSelectId}
|
||||||
|
disabled={zoomFactor === undefined}
|
||||||
onChange={onZoomSelectChange}
|
onChange={onZoomSelectChange}
|
||||||
options={zoomFactors}
|
options={zoomFactor === undefined ? [] : zoomFactors}
|
||||||
value={zoomFactor}
|
value={zoomFactor}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
@ -909,7 +930,10 @@ export function Preferences({
|
||||||
);
|
);
|
||||||
} else if (page === Page.Chats) {
|
} else if (page === Page.Chats) {
|
||||||
let spellCheckDirtyText: string | undefined;
|
let spellCheckDirtyText: string | undefined;
|
||||||
if (initialSpellCheckSetting !== hasSpellCheck) {
|
if (
|
||||||
|
hasSpellCheck !== undefined &&
|
||||||
|
initialSpellCheckSetting !== hasSpellCheck
|
||||||
|
) {
|
||||||
spellCheckDirtyText = hasSpellCheck
|
spellCheckDirtyText = hasSpellCheck
|
||||||
? i18n('icu:spellCheckWillBeEnabled')
|
? i18n('icu:spellCheckWillBeEnabled')
|
||||||
: i18n('icu:spellCheckWillBeDisabled');
|
: i18n('icu:spellCheckWillBeDisabled');
|
||||||
|
@ -927,6 +951,7 @@ export function Preferences({
|
||||||
<SettingsRow title={i18n('icu:Preferences__button--chats')}>
|
<SettingsRow title={i18n('icu:Preferences__button--chats')}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={hasSpellCheck}
|
checked={hasSpellCheck}
|
||||||
|
disabled={hasSpellCheck === undefined}
|
||||||
description={spellCheckDirtyText}
|
description={spellCheckDirtyText}
|
||||||
label={i18n('icu:spellCheckDescription')}
|
label={i18n('icu:spellCheckDescription')}
|
||||||
moduleClassName="Preferences__checkbox"
|
moduleClassName="Preferences__checkbox"
|
||||||
|
@ -1390,6 +1415,7 @@ export function Preferences({
|
||||||
<SettingsRow title={i18n('icu:Preferences__Privacy__Application')}>
|
<SettingsRow title={i18n('icu:Preferences__Privacy__Application')}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={hasContentProtection}
|
checked={hasContentProtection}
|
||||||
|
disabled={hasContentProtection === undefined}
|
||||||
description={i18n(
|
description={i18n(
|
||||||
'icu:Preferences__content-protection--description'
|
'icu:Preferences__content-protection--description'
|
||||||
)}
|
)}
|
||||||
|
@ -1811,117 +1837,125 @@ export function Preferences({
|
||||||
<div className="module-title-bar-drag-area" />
|
<div className="module-title-bar-drag-area" />
|
||||||
<div className="Preferences">
|
<div className="Preferences">
|
||||||
<div className="Preferences__page-selector">
|
<div className="Preferences__page-selector">
|
||||||
<button
|
<h1 className="Preferences__header">
|
||||||
type="button"
|
{i18n('icu:Preferences--header')}
|
||||||
className={classNames({
|
</h1>
|
||||||
Preferences__button: true,
|
{maybeUpdateDialog ? (
|
||||||
'Preferences__button--general': true,
|
<div className="module-left-pane__dialogs">{maybeUpdateDialog}</div>
|
||||||
'Preferences__button--selected': page === Page.General,
|
) : null}
|
||||||
})}
|
<div className="Preferences__scroll-area">
|
||||||
onClick={() => setPage(Page.General)}
|
|
||||||
>
|
|
||||||
{i18n('icu:Preferences__button--general')}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={classNames({
|
|
||||||
Preferences__button: true,
|
|
||||||
'Preferences__button--appearance': true,
|
|
||||||
'Preferences__button--selected':
|
|
||||||
page === Page.Appearance || page === Page.ChatColor,
|
|
||||||
})}
|
|
||||||
onClick={() => setPage(Page.Appearance)}
|
|
||||||
>
|
|
||||||
{i18n('icu:Preferences__button--appearance')}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={classNames({
|
|
||||||
Preferences__button: true,
|
|
||||||
'Preferences__button--chats': true,
|
|
||||||
'Preferences__button--selected': page === Page.Chats,
|
|
||||||
})}
|
|
||||||
onClick={() => setPage(Page.Chats)}
|
|
||||||
>
|
|
||||||
{i18n('icu:Preferences__button--chats')}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={classNames({
|
|
||||||
Preferences__button: true,
|
|
||||||
'Preferences__button--calls': true,
|
|
||||||
'Preferences__button--selected': page === Page.Calls,
|
|
||||||
})}
|
|
||||||
onClick={() => setPage(Page.Calls)}
|
|
||||||
>
|
|
||||||
{i18n('icu:Preferences__button--calls')}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={classNames({
|
|
||||||
Preferences__button: true,
|
|
||||||
'Preferences__button--notifications': true,
|
|
||||||
'Preferences__button--selected': page === Page.Notifications,
|
|
||||||
})}
|
|
||||||
onClick={() => setPage(Page.Notifications)}
|
|
||||||
>
|
|
||||||
{i18n('icu:Preferences__button--notifications')}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={classNames({
|
|
||||||
Preferences__button: true,
|
|
||||||
'Preferences__button--privacy': true,
|
|
||||||
'Preferences__button--selected':
|
|
||||||
page === Page.Privacy || page === Page.PNP,
|
|
||||||
})}
|
|
||||||
onClick={() => setPage(Page.Privacy)}
|
|
||||||
>
|
|
||||||
{i18n('icu:Preferences__button--privacy')}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={classNames({
|
|
||||||
Preferences__button: true,
|
|
||||||
'Preferences__button--data-usage': true,
|
|
||||||
'Preferences__button--selected': page === Page.DataUsage,
|
|
||||||
})}
|
|
||||||
onClick={() => setPage(Page.DataUsage)}
|
|
||||||
>
|
|
||||||
{i18n('icu:Preferences__button--data-usage')}
|
|
||||||
</button>
|
|
||||||
{shouldShowBackupsPage ? (
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={classNames({
|
className={classNames({
|
||||||
Preferences__button: true,
|
Preferences__button: true,
|
||||||
'Preferences__button--backups': true,
|
'Preferences__button--general': true,
|
||||||
'Preferences__button--selected': page === Page.Backups,
|
'Preferences__button--selected': page === Page.General,
|
||||||
})}
|
})}
|
||||||
onClick={() => setPage(Page.Backups)}
|
onClick={() => setPage(Page.General)}
|
||||||
>
|
>
|
||||||
{i18n('icu:Preferences__button--backups')}
|
{i18n('icu:Preferences__button--general')}
|
||||||
</button>
|
</button>
|
||||||
) : null}
|
|
||||||
{isInternalUser ? (
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={classNames({
|
className={classNames({
|
||||||
Preferences__button: true,
|
Preferences__button: true,
|
||||||
'Preferences__button--internal': true,
|
'Preferences__button--appearance': true,
|
||||||
'Preferences__button--selected': page === Page.Internal,
|
'Preferences__button--selected':
|
||||||
|
page === Page.Appearance || page === Page.ChatColor,
|
||||||
})}
|
})}
|
||||||
onClick={() => setPage(Page.Internal)}
|
onClick={() => setPage(Page.Appearance)}
|
||||||
>
|
>
|
||||||
{i18n('icu:Preferences__button--internal')}
|
{i18n('icu:Preferences__button--appearance')}
|
||||||
</button>
|
</button>
|
||||||
) : null}
|
<button
|
||||||
|
type="button"
|
||||||
|
className={classNames({
|
||||||
|
Preferences__button: true,
|
||||||
|
'Preferences__button--chats': true,
|
||||||
|
'Preferences__button--selected': page === Page.Chats,
|
||||||
|
})}
|
||||||
|
onClick={() => setPage(Page.Chats)}
|
||||||
|
>
|
||||||
|
{i18n('icu:Preferences__button--chats')}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={classNames({
|
||||||
|
Preferences__button: true,
|
||||||
|
'Preferences__button--calls': true,
|
||||||
|
'Preferences__button--selected': page === Page.Calls,
|
||||||
|
})}
|
||||||
|
onClick={() => setPage(Page.Calls)}
|
||||||
|
>
|
||||||
|
{i18n('icu:Preferences__button--calls')}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={classNames({
|
||||||
|
Preferences__button: true,
|
||||||
|
'Preferences__button--notifications': true,
|
||||||
|
'Preferences__button--selected': page === Page.Notifications,
|
||||||
|
})}
|
||||||
|
onClick={() => setPage(Page.Notifications)}
|
||||||
|
>
|
||||||
|
{i18n('icu:Preferences__button--notifications')}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={classNames({
|
||||||
|
Preferences__button: true,
|
||||||
|
'Preferences__button--privacy': true,
|
||||||
|
'Preferences__button--selected':
|
||||||
|
page === Page.Privacy || page === Page.PNP,
|
||||||
|
})}
|
||||||
|
onClick={() => setPage(Page.Privacy)}
|
||||||
|
>
|
||||||
|
{i18n('icu:Preferences__button--privacy')}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={classNames({
|
||||||
|
Preferences__button: true,
|
||||||
|
'Preferences__button--data-usage': true,
|
||||||
|
'Preferences__button--selected': page === Page.DataUsage,
|
||||||
|
})}
|
||||||
|
onClick={() => setPage(Page.DataUsage)}
|
||||||
|
>
|
||||||
|
{i18n('icu:Preferences__button--data-usage')}
|
||||||
|
</button>
|
||||||
|
{shouldShowBackupsPage ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={classNames({
|
||||||
|
Preferences__button: true,
|
||||||
|
'Preferences__button--backups': true,
|
||||||
|
'Preferences__button--selected': page === Page.Backups,
|
||||||
|
})}
|
||||||
|
onClick={() => setPage(Page.Backups)}
|
||||||
|
>
|
||||||
|
{i18n('icu:Preferences__button--backups')}
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
|
{isInternalUser ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={classNames({
|
||||||
|
Preferences__button: true,
|
||||||
|
'Preferences__button--internal': true,
|
||||||
|
'Preferences__button--selected': page === Page.Internal,
|
||||||
|
})}
|
||||||
|
onClick={() => setPage(Page.Internal)}
|
||||||
|
>
|
||||||
|
{i18n('icu:Preferences__button--internal')}
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="Preferences__settings-pane-spacer" />
|
||||||
<div className="Preferences__settings-pane" ref={settingsPaneRef}>
|
<div className="Preferences__settings-pane" ref={settingsPaneRef}>
|
||||||
{settings}
|
{settings}
|
||||||
</div>
|
</div>
|
||||||
|
<div className="Preferences__settings-pane-spacer" />
|
||||||
</div>
|
</div>
|
||||||
<ToastManager
|
<ToastManager
|
||||||
OS="unused"
|
OS="unused"
|
||||||
|
|
|
@ -5,16 +5,13 @@ import type { BrowserWindow } from 'electron';
|
||||||
import { ipcMain as ipc, session } from 'electron';
|
import { ipcMain as ipc, session } from 'electron';
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
|
|
||||||
|
import * as log from '../logging/log';
|
||||||
import { userConfig } from '../../app/user_config';
|
import { userConfig } from '../../app/user_config';
|
||||||
import { ephemeralConfig } from '../../app/ephemeral_config';
|
import { ephemeralConfig } from '../../app/ephemeral_config';
|
||||||
import { installPermissionsHandler } from '../../app/permissions';
|
import { installPermissionsHandler } from '../../app/permissions';
|
||||||
import { strictAssert } from '../util/assert';
|
import { strictAssert } from '../util/assert';
|
||||||
import { explodePromise } from '../util/explodePromise';
|
|
||||||
import type {
|
import type { EphemeralSettings } from '../util/preload';
|
||||||
IPCEventsValuesType,
|
|
||||||
IPCEventsCallbacksType,
|
|
||||||
} from '../util/createIPCEvents';
|
|
||||||
import type { EphemeralSettings, SettingsValuesType } from '../util/preload';
|
|
||||||
|
|
||||||
const EPHEMERAL_NAME_MAP = new Map([
|
const EPHEMERAL_NAME_MAP = new Map([
|
||||||
['spellCheck', 'spell-check'],
|
['spellCheck', 'spell-check'],
|
||||||
|
@ -24,18 +21,8 @@ const EPHEMERAL_NAME_MAP = new Map([
|
||||||
['contentProtection', 'contentProtection'],
|
['contentProtection', 'contentProtection'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
type ResponseQueueEntry = Readonly<{
|
|
||||||
resolve(value: unknown): void;
|
|
||||||
reject(error: Error): void;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
type SettingChangeEventType<Key extends keyof SettingsValuesType> =
|
|
||||||
`change:${Key}`;
|
|
||||||
|
|
||||||
export class SettingsChannel extends EventEmitter {
|
export class SettingsChannel extends EventEmitter {
|
||||||
#mainWindow?: BrowserWindow;
|
#mainWindow?: BrowserWindow;
|
||||||
readonly #responseQueue = new Map<number, ResponseQueueEntry>();
|
|
||||||
#responseSeq = 0;
|
|
||||||
|
|
||||||
public setMainWindow(mainWindow: BrowserWindow | undefined): void {
|
public setMainWindow(mainWindow: BrowserWindow | undefined): void {
|
||||||
this.#mainWindow = mainWindow;
|
this.#mainWindow = mainWindow;
|
||||||
|
@ -45,81 +32,16 @@ export class SettingsChannel extends EventEmitter {
|
||||||
return this.#mainWindow;
|
return this.#mainWindow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public openSettingsTab(): void {
|
||||||
|
if (!this.#mainWindow) {
|
||||||
|
log.warn('openSettingsTab: No mainWindow, cannot open settings tab');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.#mainWindow.webContents.send('open-settings-tab');
|
||||||
|
this.#mainWindow.show();
|
||||||
|
}
|
||||||
|
|
||||||
public install(): void {
|
public install(): void {
|
||||||
this.#installSetting('deviceName', { setter: false });
|
|
||||||
this.#installSetting('phoneNumber', { setter: false });
|
|
||||||
|
|
||||||
// ChatColorPicker redux hookups
|
|
||||||
this.#installCallback('getCustomColors');
|
|
||||||
this.#installCallback('getConversationsWithCustomColor');
|
|
||||||
this.#installCallback('resetAllChatColors');
|
|
||||||
this.#installCallback('resetDefaultChatColor');
|
|
||||||
this.#installCallback('addCustomColor');
|
|
||||||
this.#installCallback('editCustomColor');
|
|
||||||
this.#installCallback('removeCustomColor');
|
|
||||||
this.#installCallback('removeCustomColorOnConversations');
|
|
||||||
this.#installCallback('setGlobalDefaultConversationColor');
|
|
||||||
this.#installCallback('getDefaultConversationColor');
|
|
||||||
|
|
||||||
// Various callbacks
|
|
||||||
this.#installCallback('deleteAllMyStories');
|
|
||||||
this.#installCallback('getAvailableIODevices');
|
|
||||||
this.#installCallback('isPrimary');
|
|
||||||
this.#installCallback('isInternalUser');
|
|
||||||
this.#installCallback('syncRequest');
|
|
||||||
this.#installCallback('setEmojiSkinToneDefault');
|
|
||||||
this.#installCallback('getEmojiSkinToneDefault');
|
|
||||||
this.#installCallback('exportLocalBackup');
|
|
||||||
this.#installCallback('importLocalBackup');
|
|
||||||
this.#installCallback('validateBackup');
|
|
||||||
|
|
||||||
// Backups
|
|
||||||
this.#installSetting('backupFeatureEnabled', { setter: false });
|
|
||||||
this.#installSetting('cloudBackupStatus', { setter: false });
|
|
||||||
this.#installSetting('backupSubscriptionStatus', { setter: false });
|
|
||||||
this.#installCallback('refreshCloudBackupStatus');
|
|
||||||
this.#installCallback('refreshBackupSubscriptionStatus');
|
|
||||||
|
|
||||||
// Getters only. These are set by the primary device
|
|
||||||
this.#installSetting('blockedCount', { setter: false });
|
|
||||||
this.#installSetting('linkPreviewSetting', { setter: false });
|
|
||||||
this.#installSetting('readReceiptSetting', { setter: false });
|
|
||||||
this.#installSetting('typingIndicatorSetting', { setter: false });
|
|
||||||
|
|
||||||
this.#installSetting('hideMenuBar');
|
|
||||||
this.#installSetting('notificationSetting');
|
|
||||||
this.#installSetting('notificationDrawAttention');
|
|
||||||
this.#installSetting('audioMessage');
|
|
||||||
this.#installSetting('audioNotification');
|
|
||||||
this.#installSetting('countMutedConversations');
|
|
||||||
|
|
||||||
this.#installSetting('sentMediaQualitySetting');
|
|
||||||
this.#installSetting('textFormatting');
|
|
||||||
|
|
||||||
this.#installSetting('autoConvertEmoji');
|
|
||||||
this.#installSetting('autoDownloadUpdate');
|
|
||||||
this.#installSetting('autoDownloadAttachment');
|
|
||||||
this.#installSetting('autoLaunch');
|
|
||||||
|
|
||||||
this.#installSetting('alwaysRelayCalls');
|
|
||||||
this.#installSetting('callRingtoneNotification');
|
|
||||||
this.#installSetting('callSystemNotification');
|
|
||||||
this.#installSetting('incomingCallNotification');
|
|
||||||
|
|
||||||
// Media settings
|
|
||||||
this.#installSetting('preferredAudioInputDevice');
|
|
||||||
this.#installSetting('preferredAudioOutputDevice');
|
|
||||||
this.#installSetting('preferredVideoInputDevice');
|
|
||||||
|
|
||||||
this.#installSetting('lastSyncTime');
|
|
||||||
this.#installSetting('universalExpireTimer');
|
|
||||||
|
|
||||||
this.#installSetting('hasStoriesDisabled');
|
|
||||||
this.#installSetting('zoomFactor');
|
|
||||||
|
|
||||||
this.#installSetting('phoneNumberDiscoverabilitySetting');
|
|
||||||
this.#installSetting('phoneNumberSharingSetting');
|
|
||||||
|
|
||||||
this.#installEphemeralSetting('themeSetting');
|
this.#installEphemeralSetting('themeSetting');
|
||||||
this.#installEphemeralSetting('systemTraySetting');
|
this.#installEphemeralSetting('systemTraySetting');
|
||||||
this.#installEphemeralSetting('localeOverride');
|
this.#installEphemeralSetting('localeOverride');
|
||||||
|
@ -156,113 +78,6 @@ export class SettingsChannel extends EventEmitter {
|
||||||
userConfig,
|
userConfig,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
ipc.on('settings:response', (_event, seq, error, value) => {
|
|
||||||
const entry = this.#responseQueue.get(seq);
|
|
||||||
this.#responseQueue.delete(seq);
|
|
||||||
if (!entry) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { resolve, reject } = entry;
|
|
||||||
if (error) {
|
|
||||||
reject(error);
|
|
||||||
} else {
|
|
||||||
resolve(value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#waitForResponse<Value>(): { promise: Promise<Value>; seq: number } {
|
|
||||||
const seq = this.#responseSeq;
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-bitwise
|
|
||||||
this.#responseSeq = (this.#responseSeq + 1) & 0x7fffffff;
|
|
||||||
|
|
||||||
const { promise, resolve, reject } = explodePromise<Value>();
|
|
||||||
|
|
||||||
this.#responseQueue.set(seq, { resolve, reject });
|
|
||||||
|
|
||||||
return { seq, promise };
|
|
||||||
}
|
|
||||||
|
|
||||||
public getSettingFromMainWindow<Name extends keyof IPCEventsValuesType>(
|
|
||||||
name: Name
|
|
||||||
): Promise<IPCEventsValuesType[Name]> {
|
|
||||||
const mainWindow = this.#mainWindow;
|
|
||||||
if (!mainWindow || !mainWindow.webContents) {
|
|
||||||
throw new Error('No main window');
|
|
||||||
}
|
|
||||||
|
|
||||||
const { seq, promise } = this.#waitForResponse<IPCEventsValuesType[Name]>();
|
|
||||||
|
|
||||||
mainWindow.webContents.send(`settings:get:${name}`, { seq });
|
|
||||||
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
public setSettingInMainWindow<Name extends keyof IPCEventsValuesType>(
|
|
||||||
name: Name,
|
|
||||||
value: IPCEventsValuesType[Name]
|
|
||||||
): Promise<void> {
|
|
||||||
const mainWindow = this.#mainWindow;
|
|
||||||
if (!mainWindow || !mainWindow.webContents) {
|
|
||||||
throw new Error('No main window');
|
|
||||||
}
|
|
||||||
|
|
||||||
const { seq, promise } = this.#waitForResponse<void>();
|
|
||||||
|
|
||||||
mainWindow.webContents.send(`settings:set:${name}`, { seq, value });
|
|
||||||
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
public invokeCallbackInMainWindow<Name extends keyof IPCEventsCallbacksType>(
|
|
||||||
name: Name,
|
|
||||||
args: ReadonlyArray<unknown>
|
|
||||||
): Promise<unknown> {
|
|
||||||
const mainWindow = this.#mainWindow;
|
|
||||||
if (!mainWindow || !mainWindow.webContents) {
|
|
||||||
throw new Error('Main window not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
const { seq, promise } = this.#waitForResponse<unknown>();
|
|
||||||
|
|
||||||
mainWindow.webContents.send(`settings:call:${name}`, { seq, args });
|
|
||||||
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
#installCallback<Name extends keyof IPCEventsCallbacksType>(
|
|
||||||
name: Name
|
|
||||||
): void {
|
|
||||||
ipc.handle(`settings:call:${name}`, async (_event, args) => {
|
|
||||||
return this.invokeCallbackInMainWindow(name, args);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#installSetting<Name extends keyof IPCEventsValuesType>(
|
|
||||||
name: Name,
|
|
||||||
{
|
|
||||||
getter = true,
|
|
||||||
setter = true,
|
|
||||||
}: { getter?: boolean; setter?: boolean } = {}
|
|
||||||
): void {
|
|
||||||
if (getter) {
|
|
||||||
ipc.handle(`settings:get:${name}`, async () => {
|
|
||||||
return this.getSettingFromMainWindow(name);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!setter) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ipc.handle(`settings:set:${name}`, async (_event, value) => {
|
|
||||||
await this.setSettingInMainWindow(name, value);
|
|
||||||
|
|
||||||
this.emit(`change:${name}`, value);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#installEphemeralSetting<Name extends keyof EphemeralSettings>(
|
#installEphemeralSetting<Name extends keyof EphemeralSettings>(
|
||||||
|
@ -285,8 +100,6 @@ export class SettingsChannel extends EventEmitter {
|
||||||
);
|
);
|
||||||
ephemeralConfig.set(ephemeralName, value);
|
ephemeralConfig.set(ephemeralName, value);
|
||||||
|
|
||||||
this.emit(`change:${name}`, value);
|
|
||||||
|
|
||||||
// Notify main to notify windows of preferences change. As for DB-backed
|
// Notify main to notify windows of preferences change. As for DB-backed
|
||||||
// settings, those are set by the renderer, and afterwards the renderer IPC sends
|
// settings, those are set by the renderer, and afterwards the renderer IPC sends
|
||||||
// to main the event 'preferences-changed'.
|
// to main the event 'preferences-changed'.
|
||||||
|
@ -313,12 +126,6 @@ export class SettingsChannel extends EventEmitter {
|
||||||
callback: (name: string) => void
|
callback: (name: string) => void
|
||||||
): this;
|
): this;
|
||||||
|
|
||||||
public override on(
|
|
||||||
type: SettingChangeEventType<keyof SettingsValuesType>,
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
callback: (...args: Array<any>) => void
|
|
||||||
): this;
|
|
||||||
|
|
||||||
public override on(
|
public override on(
|
||||||
type: string | symbol,
|
type: string | symbol,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
@ -337,12 +144,6 @@ export class SettingsChannel extends EventEmitter {
|
||||||
name: string
|
name: string
|
||||||
): boolean;
|
): boolean;
|
||||||
|
|
||||||
public override emit(
|
|
||||||
type: SettingChangeEventType<keyof SettingsValuesType>,
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
...args: Array<any>
|
|
||||||
): boolean;
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
public override emit(type: string | symbol, ...args: Array<any>): boolean {
|
public override emit(type: string | symbol, ...args: Array<any>): boolean {
|
||||||
return super.emit(type, ...args);
|
return super.emit(type, ...args);
|
||||||
|
|
|
@ -34,6 +34,7 @@ import {
|
||||||
import { drop } from '../util/drop';
|
import { drop } from '../util/drop';
|
||||||
import { getMessageById } from '../messages/getMessageById';
|
import { getMessageById } from '../messages/getMessageById';
|
||||||
import { MessageModel } from '../models/messages';
|
import { MessageModel } from '../models/messages';
|
||||||
|
import { areStoryViewReceiptsEnabled } from '../types/Stories';
|
||||||
|
|
||||||
const { deleteSentProtoRecipient, removeSyncTasks, removeSyncTaskById } =
|
const { deleteSentProtoRecipient, removeSyncTasks, removeSyncTaskById } =
|
||||||
DataWriter;
|
DataWriter;
|
||||||
|
@ -398,7 +399,7 @@ const shouldDropReceipt = (
|
||||||
return !window.storage.get('read-receipt-setting');
|
return !window.storage.get('read-receipt-setting');
|
||||||
case messageReceiptTypeSchema.Enum.View:
|
case messageReceiptTypeSchema.Enum.View:
|
||||||
if (isStory(message)) {
|
if (isStory(message)) {
|
||||||
return !window.Events.getStoryViewReceiptsEnabled();
|
return !areStoryViewReceiptsEnabled();
|
||||||
}
|
}
|
||||||
return !window.storage.get('read-receipt-setting');
|
return !window.storage.get('read-receipt-setting');
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -193,6 +193,7 @@ import { cleanupMessages } from '../util/cleanup';
|
||||||
import { MessageModel } from './messages';
|
import { MessageModel } from './messages';
|
||||||
import { applyNewAvatar } from '../groups';
|
import { applyNewAvatar } from '../groups';
|
||||||
import { safeSetTimeout } from '../util/timeout';
|
import { safeSetTimeout } from '../util/timeout';
|
||||||
|
import { getTypingIndicatorSetting } from '../types/Util';
|
||||||
import { INITIAL_EXPIRE_TIMER_VERSION } from '../util/expirationTimer';
|
import { INITIAL_EXPIRE_TIMER_VERSION } from '../util/expirationTimer';
|
||||||
|
|
||||||
/* eslint-disable more/no-then */
|
/* eslint-disable more/no-then */
|
||||||
|
@ -1125,7 +1126,7 @@ export class ConversationModel extends window.Backbone
|
||||||
|
|
||||||
bumpTyping(): void {
|
bumpTyping(): void {
|
||||||
// We don't send typing messages if the setting is disabled
|
// We don't send typing messages if the setting is disabled
|
||||||
if (!window.Events.getTypingIndicatorSetting()) {
|
if (!getTypingIndicatorSetting()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -63,7 +63,7 @@ function _maybeGrabLinkPreview(
|
||||||
): void {
|
): void {
|
||||||
// Don't generate link previews if user has turned them off. When posting a
|
// Don't generate link previews if user has turned them off. When posting a
|
||||||
// story we should return minimal (url-only) link previews.
|
// story we should return minimal (url-only) link previews.
|
||||||
if (!window.Events.getLinkPreviewSetting() && mode === 'conversation') {
|
if (!LinkPreview.getLinkPreviewSetting() && mode === 'conversation') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,7 +102,7 @@ function _maybeGrabLinkPreview(
|
||||||
drop(
|
drop(
|
||||||
addLinkPreview(link, source, {
|
addLinkPreview(link, source, {
|
||||||
conversationId,
|
conversationId,
|
||||||
disableFetch: !window.Events.getLinkPreviewSetting(),
|
disableFetch: !LinkPreview.getLinkPreviewSetting(),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -153,6 +153,8 @@ import { generateBackupsSubscriberData } from '../../util/backupSubscriptionData
|
||||||
import { getEnvironment, isTestEnvironment } from '../../environment';
|
import { getEnvironment, isTestEnvironment } from '../../environment';
|
||||||
import { calculateLightness } from '../../util/getHSL';
|
import { calculateLightness } from '../../util/getHSL';
|
||||||
import { toDayOfWeekArray } from '../../types/NotificationProfile';
|
import { toDayOfWeekArray } from '../../types/NotificationProfile';
|
||||||
|
import { getLinkPreviewSetting } from '../../types/LinkPreview';
|
||||||
|
import { getTypingIndicatorSetting } from '../../types/Util';
|
||||||
|
|
||||||
const MAX_CONCURRENCY = 10;
|
const MAX_CONCURRENCY = 10;
|
||||||
|
|
||||||
|
@ -864,8 +866,8 @@ export class BackupExportStream extends Readable {
|
||||||
accountSettings: {
|
accountSettings: {
|
||||||
readReceipts: storage.get('read-receipt-setting'),
|
readReceipts: storage.get('read-receipt-setting'),
|
||||||
sealedSenderIndicators: storage.get('sealedSenderIndicators'),
|
sealedSenderIndicators: storage.get('sealedSenderIndicators'),
|
||||||
typingIndicators: window.Events.getTypingIndicatorSetting(),
|
typingIndicators: getTypingIndicatorSetting(),
|
||||||
linkPreviews: window.Events.getLinkPreviewSetting(),
|
linkPreviews: getLinkPreviewSetting(),
|
||||||
notDiscoverableByPhoneNumber:
|
notDiscoverableByPhoneNumber:
|
||||||
parsePhoneNumberDiscoverability(
|
parsePhoneNumberDiscoverability(
|
||||||
storage.get('phoneNumberDiscoverability')
|
storage.get('phoneNumberDiscoverability')
|
||||||
|
|
|
@ -235,6 +235,38 @@ export type SetPresentingOptionsType = Readonly<{
|
||||||
callLinkRootKey?: string;
|
callLinkRootKey?: string;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
function getIncomingCallNotification(): boolean {
|
||||||
|
return window.storage.get('incoming-call-notification', true);
|
||||||
|
}
|
||||||
|
function getAlwaysRelayCalls(): boolean {
|
||||||
|
return window.storage.get('always-relay-calls', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPreferredAudioInputDevice(): AudioDevice | undefined {
|
||||||
|
return window.storage.get('preferred-audio-input-device');
|
||||||
|
}
|
||||||
|
async function setPreferredAudioInputDevice(
|
||||||
|
device: AudioDevice
|
||||||
|
): Promise<void> {
|
||||||
|
await window.storage.put('preferred-audio-input-device', device);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPreferredAudioOutputDevice(): AudioDevice | undefined {
|
||||||
|
return window.storage.get('preferred-audio-output-device');
|
||||||
|
}
|
||||||
|
async function setPreferredAudioOutputDevice(
|
||||||
|
device: AudioDevice
|
||||||
|
): Promise<void> {
|
||||||
|
await window.storage.put('preferred-audio-output-device', device);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPreferredVideoInputDevice(): string | undefined {
|
||||||
|
return window.storage.get('preferred-video-input-device');
|
||||||
|
}
|
||||||
|
async function setPreferredVideoInputDevice(device: string): Promise<void> {
|
||||||
|
await window.storage.put('preferred-video-input-device', device);
|
||||||
|
}
|
||||||
|
|
||||||
function truncateForLogging(name: string | undefined): string | undefined {
|
function truncateForLogging(name: string | undefined): string | undefined {
|
||||||
if (!name || name.length <= 4) {
|
if (!name || name.length <= 4) {
|
||||||
return name;
|
return name;
|
||||||
|
@ -2653,7 +2685,7 @@ export class CallingClass {
|
||||||
const { availableCameras, availableMicrophones, availableSpeakers } =
|
const { availableCameras, availableMicrophones, availableSpeakers } =
|
||||||
await this.getAvailableIODevices();
|
await this.getAvailableIODevices();
|
||||||
|
|
||||||
const preferredMicrophone = window.Events.getPreferredAudioInputDevice();
|
const preferredMicrophone = getPreferredAudioInputDevice();
|
||||||
const selectedMicIndex = findBestMatchingAudioDeviceIndex(
|
const selectedMicIndex = findBestMatchingAudioDeviceIndex(
|
||||||
{
|
{
|
||||||
available: availableMicrophones,
|
available: availableMicrophones,
|
||||||
|
@ -2666,7 +2698,7 @@ export class CallingClass {
|
||||||
? availableMicrophones[selectedMicIndex]
|
? availableMicrophones[selectedMicIndex]
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const preferredSpeaker = window.Events.getPreferredAudioOutputDevice();
|
const preferredSpeaker = getPreferredAudioOutputDevice();
|
||||||
const selectedSpeakerIndex = findBestMatchingAudioDeviceIndex(
|
const selectedSpeakerIndex = findBestMatchingAudioDeviceIndex(
|
||||||
{
|
{
|
||||||
available: availableSpeakers,
|
available: availableSpeakers,
|
||||||
|
@ -2679,7 +2711,7 @@ export class CallingClass {
|
||||||
? availableSpeakers[selectedSpeakerIndex]
|
? availableSpeakers[selectedSpeakerIndex]
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const preferredCamera = window.Events.getPreferredVideoInputDevice();
|
const preferredCamera = getPreferredVideoInputDevice();
|
||||||
const selectedCamera = findBestMatchingCameraId(
|
const selectedCamera = findBestMatchingCameraId(
|
||||||
availableCameras,
|
availableCameras,
|
||||||
preferredCamera
|
preferredCamera
|
||||||
|
@ -2701,7 +2733,7 @@ export class CallingClass {
|
||||||
device.index,
|
device.index,
|
||||||
truncateForLogging(device.name)
|
truncateForLogging(device.name)
|
||||||
);
|
);
|
||||||
void window.Events.setPreferredAudioInputDevice(device);
|
drop(setPreferredAudioInputDevice(device));
|
||||||
RingRTC.setAudioInput(device.index);
|
RingRTC.setAudioInput(device.index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2711,7 +2743,7 @@ export class CallingClass {
|
||||||
device.index,
|
device.index,
|
||||||
truncateForLogging(device.name)
|
truncateForLogging(device.name)
|
||||||
);
|
);
|
||||||
void window.Events.setPreferredAudioOutputDevice(device);
|
drop(setPreferredAudioOutputDevice(device));
|
||||||
RingRTC.setAudioOutput(device.index);
|
RingRTC.setAudioOutput(device.index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2749,7 +2781,7 @@ export class CallingClass {
|
||||||
|
|
||||||
async setPreferredCamera(device: string): Promise<void> {
|
async setPreferredCamera(device: string): Promise<void> {
|
||||||
log.info('MediaDevice: setPreferredCamera', device);
|
log.info('MediaDevice: setPreferredCamera', device);
|
||||||
void window.Events.setPreferredVideoInputDevice(device);
|
drop(setPreferredVideoInputDevice(device));
|
||||||
await this.#videoCapturer.setPreferredDevice(device);
|
await this.#videoCapturer.setPreferredDevice(device);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2760,7 +2792,7 @@ export class CallingClass {
|
||||||
const logId = `CallingClass.handleCallingMessage(${envelope.timestamp})`;
|
const logId = `CallingClass.handleCallingMessage(${envelope.timestamp})`;
|
||||||
log.info(logId);
|
log.info(logId);
|
||||||
|
|
||||||
const enableIncomingCalls = window.Events.getIncomingCallNotification();
|
const enableIncomingCalls = getIncomingCallNotification();
|
||||||
if (callingMessage.offer && !enableIncomingCalls) {
|
if (callingMessage.offer && !enableIncomingCalls) {
|
||||||
// Drop offers silently if incoming call notifications are disabled.
|
// Drop offers silently if incoming call notifications are disabled.
|
||||||
log.info(`${logId}: Incoming calls are disabled, ignoring call offer.`);
|
log.info(`${logId}: Incoming calls are disabled, ignoring call offer.`);
|
||||||
|
@ -3078,7 +3110,7 @@ export class CallingClass {
|
||||||
RingRTC.cancelGroupRing(groupIdBytes, ringId, null);
|
RingRTC.cancelGroupRing(groupIdBytes, ringId, null);
|
||||||
} else if (this.#areAnyCallsActiveOrRinging()) {
|
} else if (this.#areAnyCallsActiveOrRinging()) {
|
||||||
RingRTC.cancelGroupRing(groupIdBytes, ringId, RingCancelReason.Busy);
|
RingRTC.cancelGroupRing(groupIdBytes, ringId, RingCancelReason.Busy);
|
||||||
} else if (window.Events.getIncomingCallNotification()) {
|
} else if (getIncomingCallNotification()) {
|
||||||
shouldRing = true;
|
shouldRing = true;
|
||||||
} else {
|
} else {
|
||||||
log.info(
|
log.info(
|
||||||
|
@ -3633,7 +3665,7 @@ export class CallingClass {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const shouldRelayCalls = window.Events.getAlwaysRelayCalls();
|
const shouldRelayCalls = getAlwaysRelayCalls();
|
||||||
|
|
||||||
// If the peer is not a Signal Connection, force IP hiding.
|
// If the peer is not a Signal Connection, force IP hiding.
|
||||||
const isContactUntrusted = !isSignalConnection(conversation.attributes);
|
const isContactUntrusted = !isSignalConnection(conversation.attributes);
|
||||||
|
|
|
@ -84,6 +84,12 @@ import {
|
||||||
generateBackupsSubscriberData,
|
generateBackupsSubscriberData,
|
||||||
saveBackupsSubscriberData,
|
saveBackupsSubscriberData,
|
||||||
} from '../util/backupSubscriptionData';
|
} from '../util/backupSubscriptionData';
|
||||||
|
import { getLinkPreviewSetting } from '../types/LinkPreview';
|
||||||
|
import {
|
||||||
|
getReadReceiptSetting,
|
||||||
|
getSealedSenderIndicatorSetting,
|
||||||
|
getTypingIndicatorSetting,
|
||||||
|
} from '../types/Util';
|
||||||
|
|
||||||
const MY_STORY_BYTES = uuidToBytes(MY_STORY_ID);
|
const MY_STORY_BYTES = uuidToBytes(MY_STORY_ID);
|
||||||
|
|
||||||
|
@ -338,14 +344,10 @@ export function toAccountRecord(
|
||||||
accountRecord.noteToSelfMarkedUnread = Boolean(
|
accountRecord.noteToSelfMarkedUnread = Boolean(
|
||||||
conversation.get('markedUnread')
|
conversation.get('markedUnread')
|
||||||
);
|
);
|
||||||
accountRecord.readReceipts = Boolean(window.Events.getReadReceiptSetting());
|
accountRecord.readReceipts = getReadReceiptSetting();
|
||||||
accountRecord.sealedSenderIndicators = Boolean(
|
accountRecord.sealedSenderIndicators = getSealedSenderIndicatorSetting();
|
||||||
window.storage.get('sealedSenderIndicators')
|
accountRecord.typingIndicators = getTypingIndicatorSetting();
|
||||||
);
|
accountRecord.linkPreviews = getLinkPreviewSetting();
|
||||||
accountRecord.typingIndicators = Boolean(
|
|
||||||
window.Events.getTypingIndicatorSetting()
|
|
||||||
);
|
|
||||||
accountRecord.linkPreviews = Boolean(window.Events.getLinkPreviewSetting());
|
|
||||||
|
|
||||||
const preferContactAvatars = window.storage.get('preferContactAvatars');
|
const preferContactAvatars = window.storage.get('preferContactAvatars');
|
||||||
if (preferContactAvatars !== undefined) {
|
if (preferContactAvatars !== undefined) {
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
// Copyright 2019 Signal Messenger, LLC
|
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
export function showSettings(): void {
|
|
||||||
window.IPC.showSettings();
|
|
||||||
}
|
|
|
@ -21,6 +21,7 @@ import { actions as items } from './ducks/items';
|
||||||
import { actions as lightbox } from './ducks/lightbox';
|
import { actions as lightbox } from './ducks/lightbox';
|
||||||
import { actions as linkPreviews } from './ducks/linkPreviews';
|
import { actions as linkPreviews } from './ducks/linkPreviews';
|
||||||
import { actions as mediaGallery } from './ducks/mediaGallery';
|
import { actions as mediaGallery } from './ducks/mediaGallery';
|
||||||
|
import { actions as nav } from './ducks/nav';
|
||||||
import { actions as network } from './ducks/network';
|
import { actions as network } from './ducks/network';
|
||||||
import { actions as notificationProfiles } from './ducks/notificationProfiles';
|
import { actions as notificationProfiles } from './ducks/notificationProfiles';
|
||||||
import { actions as safetyNumber } from './ducks/safetyNumber';
|
import { actions as safetyNumber } from './ducks/safetyNumber';
|
||||||
|
@ -55,6 +56,7 @@ export const actionCreators: ReduxActions = {
|
||||||
lightbox,
|
lightbox,
|
||||||
linkPreviews,
|
linkPreviews,
|
||||||
mediaGallery,
|
mediaGallery,
|
||||||
|
nav,
|
||||||
network,
|
network,
|
||||||
notificationProfiles,
|
notificationProfiles,
|
||||||
safetyNumber,
|
safetyNumber,
|
||||||
|
|
|
@ -11,6 +11,7 @@ export enum NavTab {
|
||||||
Chats = 'Chats',
|
Chats = 'Chats',
|
||||||
Calls = 'Calls',
|
Calls = 'Calls',
|
||||||
Stories = 'Stories',
|
Stories = 'Stories',
|
||||||
|
Settings = 'Settings',
|
||||||
}
|
}
|
||||||
|
|
||||||
// State
|
// State
|
||||||
|
|
|
@ -29,7 +29,11 @@ import { DataReader, DataWriter } from '../../sql/Client';
|
||||||
import { ReadStatus } from '../../messages/MessageReadStatus';
|
import { ReadStatus } from '../../messages/MessageReadStatus';
|
||||||
import { SendStatus } from '../../messages/MessageSendState';
|
import { SendStatus } from '../../messages/MessageSendState';
|
||||||
import { SafetyNumberChangeSource } from '../../components/SafetyNumberChangeDialog';
|
import { SafetyNumberChangeSource } from '../../components/SafetyNumberChangeDialog';
|
||||||
import { StoryViewDirectionType, StoryViewModeType } from '../../types/Stories';
|
import {
|
||||||
|
areStoryViewReceiptsEnabled,
|
||||||
|
StoryViewDirectionType,
|
||||||
|
StoryViewModeType,
|
||||||
|
} from '../../types/Stories';
|
||||||
import { assertDev, strictAssert } from '../../util/assert';
|
import { assertDev, strictAssert } from '../../util/assert';
|
||||||
import { drop } from '../../util/drop';
|
import { drop } from '../../util/drop';
|
||||||
import { blockSendUntilConversationsAreVerified } from '../../util/blockSendUntilConversationsAreVerified';
|
import { blockSendUntilConversationsAreVerified } from '../../util/blockSendUntilConversationsAreVerified';
|
||||||
|
@ -51,6 +55,7 @@ import {
|
||||||
getStories,
|
getStories,
|
||||||
getStoryDownloadableAttachment,
|
getStoryDownloadableAttachment,
|
||||||
} from '../selectors/stories';
|
} from '../selectors/stories';
|
||||||
|
import { setStoriesDisabled as utilSetStoriesDisabled } from '../../util/stories';
|
||||||
import { getStoryDataFromMessageAttributes } from '../../services/storyLoader';
|
import { getStoryDataFromMessageAttributes } from '../../services/storyLoader';
|
||||||
import { isGroup } from '../../util/whatTypeOfConversation';
|
import { isGroup } from '../../util/whatTypeOfConversation';
|
||||||
import { isNotNil } from '../../util/isNotNil';
|
import { isNotNil } from '../../util/isNotNil';
|
||||||
|
@ -443,10 +448,7 @@ function markStoryRead(
|
||||||
drop(viewSyncJobQueue.add({ viewSyncs }));
|
drop(viewSyncJobQueue.add({ viewSyncs }));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (!isSignalOnboardingStory && areStoryViewReceiptsEnabled()) {
|
||||||
!isSignalOnboardingStory &&
|
|
||||||
window.Events.getStoryViewReceiptsEnabled()
|
|
||||||
) {
|
|
||||||
drop(
|
drop(
|
||||||
conversationJobQueue.add({
|
conversationJobQueue.add({
|
||||||
type: conversationQueueJobEnum.enum.Receipts,
|
type: conversationQueueJobEnum.enum.Receipts,
|
||||||
|
@ -1380,7 +1382,7 @@ function setStoriesDisabled(
|
||||||
value: boolean
|
value: boolean
|
||||||
): ThunkAction<void, RootStateType, unknown, never> {
|
): ThunkAction<void, RootStateType, unknown, never> {
|
||||||
return async () => {
|
return async () => {
|
||||||
await window.Events.setHasStoriesDisabled(value);
|
await utilSetStoriesDisabled(value);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -82,6 +82,7 @@ export function initializeRedux(data: ReduxInitData): void {
|
||||||
actionCreators.mediaGallery,
|
actionCreators.mediaGallery,
|
||||||
store.dispatch
|
store.dispatch
|
||||||
),
|
),
|
||||||
|
nav: bindActionCreators(actionCreators.nav, store.dispatch),
|
||||||
network: bindActionCreators(actionCreators.network, store.dispatch),
|
network: bindActionCreators(actionCreators.network, store.dispatch),
|
||||||
notificationProfiles: bindActionCreators(
|
notificationProfiles: bindActionCreators(
|
||||||
actionCreators.notificationProfiles,
|
actionCreators.notificationProfiles,
|
||||||
|
|
|
@ -63,6 +63,9 @@ import { getActiveProfile } from '../selectors/notificationProfiles';
|
||||||
function renderDeviceSelection(): JSX.Element {
|
function renderDeviceSelection(): JSX.Element {
|
||||||
return <SmartCallingDeviceSelection />;
|
return <SmartCallingDeviceSelection />;
|
||||||
}
|
}
|
||||||
|
function getCallSystemNotification() {
|
||||||
|
return window.storage.get('call-system-notification', true);
|
||||||
|
}
|
||||||
|
|
||||||
const getGroupCallVideoFrameSource =
|
const getGroupCallVideoFrameSource =
|
||||||
callingService.getGroupCallVideoFrameSource.bind(callingService);
|
callingService.getGroupCallVideoFrameSource.bind(callingService);
|
||||||
|
@ -74,7 +77,7 @@ async function notifyForCall(
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const shouldNotify =
|
const shouldNotify =
|
||||||
!window.SignalContext.activeWindowService.isActive() &&
|
!window.SignalContext.activeWindowService.isActive() &&
|
||||||
window.Events.getCallSystemNotification();
|
getCallSystemNotification();
|
||||||
if (!shouldNotify) {
|
if (!shouldNotify) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,7 +63,7 @@ export const SmartChatColorPicker = memo(function SmartChatColorPicker({
|
||||||
};
|
};
|
||||||
|
|
||||||
const getConversationsWithCustomColor = useCallback(
|
const getConversationsWithCustomColor = useCallback(
|
||||||
async (colorId: string): Promise<Array<ConversationType>> => {
|
(colorId: string): Array<ConversationType> => {
|
||||||
return conversationWithCustomColorSelector(colorId);
|
return conversationWithCustomColorSelector(colorId);
|
||||||
},
|
},
|
||||||
[conversationWithCustomColorSelector]
|
[conversationWithCustomColorSelector]
|
||||||
|
|
|
@ -20,6 +20,7 @@ import {
|
||||||
getInboxEnvelopeTimestamp,
|
getInboxEnvelopeTimestamp,
|
||||||
getInboxFirstEnvelopeTimestamp,
|
getInboxFirstEnvelopeTimestamp,
|
||||||
} from '../selectors/inbox';
|
} from '../selectors/inbox';
|
||||||
|
import { SmartPreferences } from './Preferences';
|
||||||
|
|
||||||
function renderChatsTab() {
|
function renderChatsTab() {
|
||||||
return <SmartChatsTab />;
|
return <SmartChatsTab />;
|
||||||
|
@ -41,6 +42,10 @@ function renderStoriesTab() {
|
||||||
return <SmartStoriesTab />;
|
return <SmartStoriesTab />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderSettingsTab() {
|
||||||
|
return <SmartPreferences />;
|
||||||
|
}
|
||||||
|
|
||||||
export const SmartInbox = memo(function SmartInbox(): JSX.Element {
|
export const SmartInbox = memo(function SmartInbox(): JSX.Element {
|
||||||
const i18n = useSelector(getIntl);
|
const i18n = useSelector(getIntl);
|
||||||
const isCustomizingPreferredReactions = useSelector(
|
const isCustomizingPreferredReactions = useSelector(
|
||||||
|
@ -70,6 +75,7 @@ export const SmartInbox = memo(function SmartInbox(): JSX.Element {
|
||||||
}
|
}
|
||||||
renderNavTabs={renderNavTabs}
|
renderNavTabs={renderNavTabs}
|
||||||
renderStoriesTab={renderStoriesTab}
|
renderStoriesTab={renderStoriesTab}
|
||||||
|
renderSettingsTab={renderSettingsTab}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -15,9 +15,7 @@ import {
|
||||||
getHasAnyFailedStorySends,
|
getHasAnyFailedStorySends,
|
||||||
getStoriesNotificationCount,
|
getStoriesNotificationCount,
|
||||||
} from '../selectors/stories';
|
} from '../selectors/stories';
|
||||||
import { showSettings } from '../../shims/Whisper';
|
|
||||||
import { useGlobalModalActions } from '../ducks/globalModals';
|
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||||
import { useUpdatesActions } from '../ducks/updates';
|
|
||||||
import { getStoriesEnabled } from '../selectors/items';
|
import { getStoriesEnabled } from '../selectors/items';
|
||||||
import { getSelectedNavTab } from '../selectors/nav';
|
import { getSelectedNavTab } from '../selectors/nav';
|
||||||
import type { NavTab } from '../ducks/nav';
|
import type { NavTab } from '../ducks/nav';
|
||||||
|
@ -31,6 +29,7 @@ export type SmartNavTabsProps = Readonly<{
|
||||||
renderCallsTab: () => ReactNode;
|
renderCallsTab: () => ReactNode;
|
||||||
renderChatsTab: () => ReactNode;
|
renderChatsTab: () => ReactNode;
|
||||||
renderStoriesTab: () => ReactNode;
|
renderStoriesTab: () => ReactNode;
|
||||||
|
renderSettingsTab: () => ReactNode;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export const SmartNavTabs = memo(function SmartNavTabs({
|
export const SmartNavTabs = memo(function SmartNavTabs({
|
||||||
|
@ -39,6 +38,7 @@ export const SmartNavTabs = memo(function SmartNavTabs({
|
||||||
renderCallsTab,
|
renderCallsTab,
|
||||||
renderChatsTab,
|
renderChatsTab,
|
||||||
renderStoriesTab,
|
renderStoriesTab,
|
||||||
|
renderSettingsTab,
|
||||||
}: SmartNavTabsProps): JSX.Element {
|
}: SmartNavTabsProps): JSX.Element {
|
||||||
const i18n = useSelector(getIntl);
|
const i18n = useSelector(getIntl);
|
||||||
const selectedNavTab = useSelector(getSelectedNavTab);
|
const selectedNavTab = useSelector(getSelectedNavTab);
|
||||||
|
@ -54,7 +54,6 @@ export const SmartNavTabs = memo(function SmartNavTabs({
|
||||||
const hasPendingUpdate = useSelector(getHasPendingUpdate);
|
const hasPendingUpdate = useSelector(getHasPendingUpdate);
|
||||||
|
|
||||||
const { toggleProfileEditor } = useGlobalModalActions();
|
const { toggleProfileEditor } = useGlobalModalActions();
|
||||||
const { startUpdate } = useUpdatesActions();
|
|
||||||
|
|
||||||
const onNavTabSelected = useCallback(
|
const onNavTabSelected = useCallback(
|
||||||
(tab: NavTab) => {
|
(tab: NavTab) => {
|
||||||
|
@ -75,14 +74,13 @@ export const SmartNavTabs = memo(function SmartNavTabs({
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
me={me}
|
me={me}
|
||||||
navTabsCollapsed={navTabsCollapsed}
|
navTabsCollapsed={navTabsCollapsed}
|
||||||
onShowSettings={showSettings}
|
|
||||||
onStartUpdate={startUpdate}
|
|
||||||
onNavTabSelected={onNavTabSelected}
|
onNavTabSelected={onNavTabSelected}
|
||||||
onToggleNavTabsCollapse={onToggleNavTabsCollapse}
|
onToggleNavTabsCollapse={onToggleNavTabsCollapse}
|
||||||
onToggleProfileEditor={toggleProfileEditor}
|
onToggleProfileEditor={toggleProfileEditor}
|
||||||
renderCallsTab={renderCallsTab}
|
renderCallsTab={renderCallsTab}
|
||||||
renderChatsTab={renderChatsTab}
|
renderChatsTab={renderChatsTab}
|
||||||
renderStoriesTab={renderStoriesTab}
|
renderStoriesTab={renderStoriesTab}
|
||||||
|
renderSettingsTab={renderSettingsTab}
|
||||||
selectedNavTab={selectedNavTab}
|
selectedNavTab={selectedNavTab}
|
||||||
storiesEnabled={storiesEnabled}
|
storiesEnabled={storiesEnabled}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
|
|
705
ts/state/smart/Preferences.tsx
Normal file
705
ts/state/smart/Preferences.tsx
Normal file
|
@ -0,0 +1,705 @@
|
||||||
|
// Copyright 2024 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import React, { StrictMode, useEffect } from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import type { AudioDevice } from '@signalapp/ringrtc';
|
||||||
|
|
||||||
|
import { useItemsActions } from '../ducks/items';
|
||||||
|
import { useConversationsActions } from '../ducks/conversations';
|
||||||
|
import { getConversationsWithCustomColorSelector } from '../selectors/conversations';
|
||||||
|
import { getCustomColors, getItems } from '../selectors/items';
|
||||||
|
import { DEFAULT_AUTO_DOWNLOAD_ATTACHMENT } from '../../textsecure/Storage';
|
||||||
|
import { DEFAULT_CONVERSATION_COLOR } from '../../types/Colors';
|
||||||
|
import { isBackupFeatureEnabledForRedux } from '../../util/isBackupEnabled';
|
||||||
|
import { format } from '../../types/PhoneNumber';
|
||||||
|
import { getIntl, getUserDeviceId, getUserNumber } from '../selectors/user';
|
||||||
|
import { EmojiSkinTone } from '../../components/fun/data/emojis';
|
||||||
|
import { renderClearingDataView } from '../../shims/renderClearingDataView';
|
||||||
|
import OS from '../../util/os/osPreload';
|
||||||
|
import { themeChanged } from '../../shims/themeChanged';
|
||||||
|
import * as Settings from '../../types/Settings';
|
||||||
|
import * as universalExpireTimerUtil from '../../util/universalExpireTimer';
|
||||||
|
import {
|
||||||
|
parseSystemTraySetting,
|
||||||
|
shouldMinimizeToSystemTray,
|
||||||
|
SystemTraySetting,
|
||||||
|
} from '../../types/SystemTraySetting';
|
||||||
|
import { calling } from '../../services/calling';
|
||||||
|
import { drop } from '../../util/drop';
|
||||||
|
import { assertDev, strictAssert } from '../../util/assert';
|
||||||
|
import { backupsService } from '../../services/backups';
|
||||||
|
import { DurationInSeconds } from '../../util/durations/duration-in-seconds';
|
||||||
|
import { PhoneNumberDiscoverability } from '../../util/phoneNumberDiscoverability';
|
||||||
|
import { PhoneNumberSharingMode } from '../../util/phoneNumberSharingMode';
|
||||||
|
import { writeProfile } from '../../services/writeProfile';
|
||||||
|
import { getConversation } from '../../util/getConversation';
|
||||||
|
import { waitForEvent } from '../../shims/events';
|
||||||
|
import { MINUTE } from '../../util/durations';
|
||||||
|
import { sendSyncRequests } from '../../textsecure/syncRequests';
|
||||||
|
|
||||||
|
import { SmartUpdateDialog } from './UpdateDialog';
|
||||||
|
import { Preferences } from '../../components/Preferences';
|
||||||
|
|
||||||
|
import type { StorageAccessType, ZoomFactorType } from '../../types/Storage';
|
||||||
|
import type { ThemeType } from '../../util/preload';
|
||||||
|
import type { WidthBreakpoint } from '../../components/_util';
|
||||||
|
import { useUpdatesActions } from '../ducks/updates';
|
||||||
|
import {
|
||||||
|
getHasPendingUpdate,
|
||||||
|
isUpdateDownloaded as getIsUpdateDownloaded,
|
||||||
|
} from '../selectors/updates';
|
||||||
|
|
||||||
|
const DEFAULT_NOTIFICATION_SETTING = 'message';
|
||||||
|
|
||||||
|
function renderUpdateDialog(
|
||||||
|
props: Readonly<{ containerWidthBreakpoint: WidthBreakpoint }>
|
||||||
|
): JSX.Element {
|
||||||
|
return <SmartUpdateDialog {...props} disableDismiss />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSystemTraySettingValues(
|
||||||
|
systemTraySetting: SystemTraySetting | undefined
|
||||||
|
): {
|
||||||
|
hasMinimizeToAndStartInSystemTray: boolean | undefined;
|
||||||
|
hasMinimizeToSystemTray: boolean | undefined;
|
||||||
|
} {
|
||||||
|
if (systemTraySetting === undefined) {
|
||||||
|
return {
|
||||||
|
hasMinimizeToAndStartInSystemTray: undefined,
|
||||||
|
hasMinimizeToSystemTray: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedSystemTraySetting = parseSystemTraySetting(systemTraySetting);
|
||||||
|
const hasMinimizeToAndStartInSystemTray =
|
||||||
|
parsedSystemTraySetting ===
|
||||||
|
SystemTraySetting.MinimizeToAndStartInSystemTray;
|
||||||
|
const hasMinimizeToSystemTray = shouldMinimizeToSystemTray(
|
||||||
|
parsedSystemTraySetting
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
hasMinimizeToAndStartInSystemTray,
|
||||||
|
hasMinimizeToSystemTray,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SmartPreferences(): JSX.Element {
|
||||||
|
const {
|
||||||
|
addCustomColor,
|
||||||
|
editCustomColor,
|
||||||
|
putItem,
|
||||||
|
removeCustomColor,
|
||||||
|
resetDefaultChatColor,
|
||||||
|
setEmojiSkinToneDefault: onEmojiSkinToneDefaultChange,
|
||||||
|
setGlobalDefaultConversationColor,
|
||||||
|
} = useItemsActions();
|
||||||
|
const { removeCustomColorOnConversations, resetAllChatColors } =
|
||||||
|
useConversationsActions();
|
||||||
|
const { startUpdate } = useUpdatesActions();
|
||||||
|
|
||||||
|
// Selectors
|
||||||
|
|
||||||
|
const customColors = useSelector(getCustomColors) ?? {};
|
||||||
|
const getConversationsWithCustomColor = useSelector(
|
||||||
|
getConversationsWithCustomColorSelector
|
||||||
|
);
|
||||||
|
const items = useSelector(getItems);
|
||||||
|
const i18n = useSelector(getIntl);
|
||||||
|
const hasPendingUpdate = useSelector(getHasPendingUpdate);
|
||||||
|
const isUpdateDownloaded = useSelector(getIsUpdateDownloaded);
|
||||||
|
|
||||||
|
// The weird ones
|
||||||
|
|
||||||
|
const makeSyncRequest = async () => {
|
||||||
|
const contactSyncComplete = waitForEvent(
|
||||||
|
'contactSync:complete',
|
||||||
|
5 * MINUTE
|
||||||
|
);
|
||||||
|
return Promise.all([sendSyncRequests(), contactSyncComplete]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const universalExpireTimer = universalExpireTimerUtil.getForRedux(items);
|
||||||
|
const onUniversalExpireTimerChange = async (newValue: number) => {
|
||||||
|
await universalExpireTimerUtil.set(DurationInSeconds.fromMillis(newValue));
|
||||||
|
|
||||||
|
// Update account in Storage Service
|
||||||
|
const account = window.ConversationController.getOurConversationOrThrow();
|
||||||
|
account.captureChange('universalExpireTimer');
|
||||||
|
|
||||||
|
// Add a notification to the currently open conversation
|
||||||
|
const state = window.reduxStore.getState();
|
||||||
|
const selectedId = state.conversations.selectedConversationId;
|
||||||
|
if (selectedId) {
|
||||||
|
const conversation = window.ConversationController.get(selectedId);
|
||||||
|
assertDev(conversation, "Conversation wasn't found");
|
||||||
|
|
||||||
|
await conversation.updateLastMessage();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateBackup = () => backupsService._internalValidate();
|
||||||
|
const exportLocalBackup = () => backupsService._internalExportLocalBackup();
|
||||||
|
const importLocalBackup = () =>
|
||||||
|
backupsService._internalStageLocalBackupForImport();
|
||||||
|
const doDeleteAllData = () => renderClearingDataView();
|
||||||
|
const refreshCloudBackupStatus =
|
||||||
|
window.Signal.Services.backups.throttledFetchCloudBackupStatus;
|
||||||
|
const refreshBackupSubscriptionStatus =
|
||||||
|
window.Signal.Services.backups.throttledFetchSubscriptionStatus;
|
||||||
|
|
||||||
|
// Context - these don't change per startup
|
||||||
|
|
||||||
|
const version = window.SignalContext.getVersion();
|
||||||
|
const availableLocales = window.SignalContext.getI18nAvailableLocales();
|
||||||
|
const resolvedLocale = window.SignalContext.getI18nLocale();
|
||||||
|
const preferredSystemLocales =
|
||||||
|
window.SignalContext.getPreferredSystemLocales();
|
||||||
|
const initialSpellCheckSetting =
|
||||||
|
window.SignalContext.config.appStartInitialSpellcheckSetting;
|
||||||
|
|
||||||
|
// Settings - these capabilities are unchanging
|
||||||
|
|
||||||
|
const isAutoDownloadUpdatesSupported =
|
||||||
|
Settings.isAutoDownloadUpdatesSupported(OS, version);
|
||||||
|
const isAutoLaunchSupported = Settings.isAutoLaunchSupported(OS);
|
||||||
|
const isHideMenuBarSupported = Settings.isHideMenuBarSupported(OS);
|
||||||
|
const isMinimizeToAndStartInSystemTraySupported =
|
||||||
|
Settings.isMinimizeToAndStartInSystemTraySupported(OS);
|
||||||
|
const isNotificationAttentionSupported =
|
||||||
|
Settings.isDrawAttentionSupported(OS);
|
||||||
|
const isSystemTraySupported = Settings.isSystemTraySupported(OS);
|
||||||
|
|
||||||
|
// Textsecure - user can change number and change this device's name
|
||||||
|
|
||||||
|
const phoneNumber = format(useSelector(getUserNumber) ?? '', {});
|
||||||
|
const isPrimary = useSelector(getUserDeviceId) === 1;
|
||||||
|
const isSyncSupported = !isPrimary;
|
||||||
|
|
||||||
|
const [deviceName, setDeviceName] = React.useState(
|
||||||
|
window.textsecure.storage.user.getDeviceName()
|
||||||
|
);
|
||||||
|
useEffect(() => {
|
||||||
|
let canceled = false;
|
||||||
|
const onDeviceNameChanged = () => {
|
||||||
|
const value = window.textsecure.storage.user.getDeviceName();
|
||||||
|
if (canceled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setDeviceName(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.Whisper.events.on('deviceNameChanged', onDeviceNameChanged);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
canceled = true;
|
||||||
|
window.Whisper.events.off('deviceNameChanged', onDeviceNameChanged);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// RingRTC - the list of devices is unchanging while settings window is open
|
||||||
|
|
||||||
|
// The select boxes for devices are disabled while these arrays have zero length
|
||||||
|
const [availableCameras, setAvailableCameras] = React.useState<
|
||||||
|
Array<MediaDeviceInfo>
|
||||||
|
>([]);
|
||||||
|
const [availableMicrophones, setAvailableMicrophones] = React.useState<
|
||||||
|
Array<AudioDevice>
|
||||||
|
>([]);
|
||||||
|
const [availableSpeakers, setAvailableSpeakers] = React.useState<
|
||||||
|
Array<AudioDevice>
|
||||||
|
>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let canceled = false;
|
||||||
|
const loadDevices = async () => {
|
||||||
|
const {
|
||||||
|
availableCameras: cameras,
|
||||||
|
availableMicrophones: microphones,
|
||||||
|
availableSpeakers: speakers,
|
||||||
|
} = await calling.getAvailableIODevices();
|
||||||
|
|
||||||
|
if (canceled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setAvailableCameras(cameras);
|
||||||
|
setAvailableMicrophones(microphones);
|
||||||
|
setAvailableSpeakers(speakers);
|
||||||
|
};
|
||||||
|
drop(loadDevices());
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
canceled = true;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Ephemeral settings, via async IPC, all can be modiified
|
||||||
|
|
||||||
|
const [localeOverride, setLocaleOverride] = React.useState<string | null>();
|
||||||
|
const [systemTraySettings, setSystemTraySettings] =
|
||||||
|
React.useState<SystemTraySetting>();
|
||||||
|
const [hasContentProtection, setContentProtection] =
|
||||||
|
React.useState<boolean>();
|
||||||
|
const [hasSpellCheck, setSpellCheck] = React.useState<boolean>();
|
||||||
|
const [themeSetting, setThemeSetting] = React.useState<ThemeType>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let canceled = false;
|
||||||
|
|
||||||
|
const loadOverride = async () => {
|
||||||
|
const value = await window.Events.getLocaleOverride();
|
||||||
|
if (canceled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLocaleOverride(value);
|
||||||
|
};
|
||||||
|
drop(loadOverride());
|
||||||
|
|
||||||
|
const loadSystemTraySettings = async () => {
|
||||||
|
const value = await window.Events.getSystemTraySetting();
|
||||||
|
if (canceled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSystemTraySettings(value);
|
||||||
|
};
|
||||||
|
drop(loadSystemTraySettings());
|
||||||
|
|
||||||
|
const loadSpellCheck = async () => {
|
||||||
|
const value = await window.Events.getSpellCheck();
|
||||||
|
if (canceled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSpellCheck(value);
|
||||||
|
};
|
||||||
|
drop(loadSpellCheck());
|
||||||
|
|
||||||
|
const loadContentProtection = async () => {
|
||||||
|
const value = await window.Events.getContentProtection();
|
||||||
|
setContentProtection(value);
|
||||||
|
};
|
||||||
|
drop(loadContentProtection());
|
||||||
|
|
||||||
|
const loadThemeSetting = async () => {
|
||||||
|
const value = await window.Events.getThemeSetting();
|
||||||
|
if (canceled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setThemeSetting(value);
|
||||||
|
};
|
||||||
|
drop(loadThemeSetting());
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
canceled = true;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onLocaleChange = async (locale: string | null | undefined) => {
|
||||||
|
setLocaleOverride(locale);
|
||||||
|
await window.Events.setLocaleOverride(locale ?? null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const { hasMinimizeToAndStartInSystemTray, hasMinimizeToSystemTray } =
|
||||||
|
getSystemTraySettingValues(systemTraySettings);
|
||||||
|
|
||||||
|
const onMinimizeToSystemTrayChange = async (value: boolean) => {
|
||||||
|
const newSetting = value
|
||||||
|
? SystemTraySetting.MinimizeToSystemTray
|
||||||
|
: SystemTraySetting.DoNotUseSystemTray;
|
||||||
|
setSystemTraySettings(newSetting);
|
||||||
|
await window.Events.setSystemTraySetting(newSetting);
|
||||||
|
};
|
||||||
|
const onMinimizeToAndStartInSystemTrayChange = async (value: boolean) => {
|
||||||
|
const newSetting = value
|
||||||
|
? SystemTraySetting.MinimizeToAndStartInSystemTray
|
||||||
|
: SystemTraySetting.MinimizeToSystemTray;
|
||||||
|
setSystemTraySettings(newSetting);
|
||||||
|
await window.Events.setSystemTraySetting(newSetting);
|
||||||
|
};
|
||||||
|
const onSpellCheckChange = async (value: boolean) => {
|
||||||
|
setSpellCheck(value);
|
||||||
|
await window.Events.setSpellCheck(value);
|
||||||
|
};
|
||||||
|
const onContentProtectionChange = async (value: boolean) => {
|
||||||
|
setContentProtection(value);
|
||||||
|
await window.Events.setContentProtection(value);
|
||||||
|
};
|
||||||
|
const onThemeChange = (value: ThemeType) => {
|
||||||
|
setThemeSetting(value);
|
||||||
|
drop(window.Events.setThemeSetting(value));
|
||||||
|
drop(themeChanged());
|
||||||
|
};
|
||||||
|
|
||||||
|
// Async IPC for electron configuration, all can be modified
|
||||||
|
|
||||||
|
const [hasAutoLaunch, setAutoLaunch] = React.useState<boolean>();
|
||||||
|
const [hasMediaCameraPermissions, setMediaCameraPermissions] =
|
||||||
|
React.useState<boolean>();
|
||||||
|
const [hasMediaPermissions, setMediaPermissions] = React.useState<boolean>();
|
||||||
|
const [zoomFactor, setZoomFactor] = React.useState<ZoomFactorType>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let canceled = false;
|
||||||
|
|
||||||
|
const loadAutoLaunch = async () => {
|
||||||
|
const value = await window.Events.getAutoLaunch();
|
||||||
|
if (canceled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setAutoLaunch(value);
|
||||||
|
};
|
||||||
|
drop(loadAutoLaunch());
|
||||||
|
|
||||||
|
const loadMediaCameraPermissions = async () => {
|
||||||
|
const value = await window.Events.getMediaCameraPermissions();
|
||||||
|
if (canceled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setMediaCameraPermissions(value);
|
||||||
|
};
|
||||||
|
drop(loadMediaCameraPermissions());
|
||||||
|
|
||||||
|
const loadMediaPermissions = async () => {
|
||||||
|
const value = await window.Events.getMediaPermissions();
|
||||||
|
if (canceled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setMediaPermissions(value);
|
||||||
|
};
|
||||||
|
drop(loadMediaPermissions());
|
||||||
|
|
||||||
|
const loadZoomFactor = async () => {
|
||||||
|
const value = await window.Events.getZoomFactor();
|
||||||
|
if (canceled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setZoomFactor(value);
|
||||||
|
};
|
||||||
|
drop(loadZoomFactor());
|
||||||
|
|
||||||
|
// We need to be ready for zoom changes from the keyboard
|
||||||
|
const updateZoomFactorFromIpc = (value: ZoomFactorType) => {
|
||||||
|
if (canceled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setZoomFactor(value);
|
||||||
|
};
|
||||||
|
window.Events.onZoomFactorChange(updateZoomFactorFromIpc);
|
||||||
|
return () => {
|
||||||
|
canceled = true;
|
||||||
|
window.Events.offZoomFactorChange(updateZoomFactorFromIpc);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onAutoLaunchChange = async (value: boolean) => {
|
||||||
|
setAutoLaunch(value);
|
||||||
|
await window.Events.setAutoLaunch(value);
|
||||||
|
};
|
||||||
|
const onZoomFactorChange = async (value: ZoomFactorType) => {
|
||||||
|
setZoomFactor(value);
|
||||||
|
await window.Events.setZoomFactor(value);
|
||||||
|
};
|
||||||
|
const onMediaCameraPermissionsChange = async (value: boolean) => {
|
||||||
|
setMediaCameraPermissions(value);
|
||||||
|
await window.IPC.setMediaCameraPermissions(value);
|
||||||
|
};
|
||||||
|
const onMediaPermissionsChange = async (value: boolean) => {
|
||||||
|
setMediaPermissions(value);
|
||||||
|
await window.IPC.setMediaPermissions(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Simple, one-way items
|
||||||
|
|
||||||
|
const { backupSubscriptionStatus, cloudBackupStatus } = items;
|
||||||
|
const defaultConversationColor =
|
||||||
|
items.defaultConversationColor || DEFAULT_CONVERSATION_COLOR;
|
||||||
|
const hasLinkPreviews = items.linkPreviews ?? false;
|
||||||
|
const hasReadReceipts = items['read-receipt-setting'] ?? false;
|
||||||
|
const hasTypingIndicators = items.typingIndicators ?? false;
|
||||||
|
const blockedCount =
|
||||||
|
(items['blocked-groups']?.length ?? 0) +
|
||||||
|
(items['blocked-uuids']?.length ?? 0);
|
||||||
|
const emojiSkinToneDefault = items.emojiSkinToneDefault ?? EmojiSkinTone.None;
|
||||||
|
const isInternalUser =
|
||||||
|
items.remoteConfig?.['desktop.internalUser']?.enabled ?? false;
|
||||||
|
const isContentProtectionSupported =
|
||||||
|
Settings.isContentProtectionSupported(OS);
|
||||||
|
const isContentProtectionNeeded = Settings.isContentProtectionNeeded(OS);
|
||||||
|
|
||||||
|
const backupFeatureEnabled = isBackupFeatureEnabledForRedux(
|
||||||
|
items.remoteConfig
|
||||||
|
);
|
||||||
|
|
||||||
|
// Two-way items
|
||||||
|
|
||||||
|
function createItemsAccess<K extends keyof StorageAccessType>(
|
||||||
|
key: K,
|
||||||
|
defaultValue: StorageAccessType[K],
|
||||||
|
callback?: (value: StorageAccessType[K]) => void
|
||||||
|
): [StorageAccessType[K], (value: StorageAccessType[K]) => void] {
|
||||||
|
const value = items[key] ?? defaultValue;
|
||||||
|
const setter = (newValue: StorageAccessType[K]) => {
|
||||||
|
putItem(key, newValue);
|
||||||
|
callback?.(newValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
return [value, setter];
|
||||||
|
}
|
||||||
|
|
||||||
|
const [autoDownloadAttachment, onAutoDownloadAttachmentChange] =
|
||||||
|
createItemsAccess(
|
||||||
|
'auto-download-attachment',
|
||||||
|
DEFAULT_AUTO_DOWNLOAD_ATTACHMENT
|
||||||
|
);
|
||||||
|
const [hasAudioNotifications, onAudioNotificationsChange] = createItemsAccess(
|
||||||
|
'audio-notification',
|
||||||
|
false
|
||||||
|
);
|
||||||
|
const [hasAutoConvertEmoji, onAutoConvertEmojiChange] = createItemsAccess(
|
||||||
|
'autoConvertEmoji',
|
||||||
|
true
|
||||||
|
);
|
||||||
|
const [hasAutoDownloadUpdate, onAutoDownloadUpdateChange] = createItemsAccess(
|
||||||
|
'auto-download-update',
|
||||||
|
true
|
||||||
|
);
|
||||||
|
const [hasCallNotifications, onCallNotificationsChange] = createItemsAccess(
|
||||||
|
'call-system-notification',
|
||||||
|
true
|
||||||
|
);
|
||||||
|
const [hasIncomingCallNotifications, onIncomingCallNotificationsChange] =
|
||||||
|
createItemsAccess('incoming-call-notification', true);
|
||||||
|
const [hasCallRingtoneNotification, onCallRingtoneNotificationChange] =
|
||||||
|
createItemsAccess('call-ringtone-notification', true);
|
||||||
|
const [hasCountMutedConversations, onCountMutedConversationsChange] =
|
||||||
|
createItemsAccess('badge-count-muted-conversations', false, () => {
|
||||||
|
window.Whisper.events.trigger('updateUnreadCount');
|
||||||
|
});
|
||||||
|
const [hasHideMenuBar, onHideMenuBarChange] = createItemsAccess(
|
||||||
|
'hide-menu-bar',
|
||||||
|
false,
|
||||||
|
value => {
|
||||||
|
window.IPC.setAutoHideMenuBar(value);
|
||||||
|
window.IPC.setMenuBarVisibility(!value);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const [hasMessageAudio, onMessageAudioChange] = createItemsAccess(
|
||||||
|
'audioMessage',
|
||||||
|
false
|
||||||
|
);
|
||||||
|
const [hasNotificationAttention, onNotificationAttentionChange] =
|
||||||
|
createItemsAccess('notification-draw-attention', false);
|
||||||
|
|
||||||
|
const [notificationContent, onNotificationContentChange] = createItemsAccess(
|
||||||
|
'notification-setting',
|
||||||
|
'message'
|
||||||
|
);
|
||||||
|
const hasNotifications = notificationContent !== 'off';
|
||||||
|
const onNotificationsChange = (value: boolean) => {
|
||||||
|
putItem(
|
||||||
|
'notification-setting',
|
||||||
|
value ? DEFAULT_NOTIFICATION_SETTING : 'off'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const [hasRelayCalls, onRelayCallsChange] = createItemsAccess(
|
||||||
|
'always-relay-calls',
|
||||||
|
false
|
||||||
|
);
|
||||||
|
const [hasStoriesDisabled, onHasStoriesDisabledChanged] = createItemsAccess(
|
||||||
|
'hasStoriesDisabled',
|
||||||
|
false,
|
||||||
|
value => {
|
||||||
|
const account = window.ConversationController.getOurConversationOrThrow();
|
||||||
|
account.captureChange('hasStoriesDisabled');
|
||||||
|
window.textsecure.server?.onHasStoriesDisabledChange(value);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const [hasTextFormatting, onTextFormattingChange] = createItemsAccess(
|
||||||
|
'textFormatting',
|
||||||
|
true
|
||||||
|
);
|
||||||
|
const [lastSyncTime, onLastSyncTimeChange] = createItemsAccess(
|
||||||
|
'synced_at',
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
const [selectedCamera, onSelectedCameraChange] = createItemsAccess(
|
||||||
|
'preferred-video-input-device',
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
const [selectedMicrophone, onSelectedMicrophoneChange] = createItemsAccess(
|
||||||
|
'preferred-audio-input-device',
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
const [selectedSpeaker, onSelectedSpeakerChange] = createItemsAccess(
|
||||||
|
'preferred-audio-output-device',
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
const [sentMediaQualitySetting, onSentMediaQualityChange] = createItemsAccess(
|
||||||
|
'sent-media-quality',
|
||||||
|
'standard'
|
||||||
|
);
|
||||||
|
|
||||||
|
const [whoCanFindMe, onWhoCanFindMeChange] = createItemsAccess(
|
||||||
|
'phoneNumberDiscoverability',
|
||||||
|
PhoneNumberDiscoverability.NotDiscoverable,
|
||||||
|
async (newValue: PhoneNumberDiscoverability) => {
|
||||||
|
strictAssert(window.textsecure.server, 'WebAPI must be available');
|
||||||
|
await window.textsecure.server.setPhoneNumberDiscoverability(
|
||||||
|
newValue === PhoneNumberDiscoverability.Discoverable
|
||||||
|
);
|
||||||
|
const account = window.ConversationController.getOurConversationOrThrow();
|
||||||
|
account.captureChange('phoneNumberDiscoverability');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const [whoCanSeeMe, onWhoCanSeeMeChange] = createItemsAccess(
|
||||||
|
'phoneNumberSharingMode',
|
||||||
|
PhoneNumberSharingMode.Nobody,
|
||||||
|
async (newValue: PhoneNumberSharingMode) => {
|
||||||
|
const account = window.ConversationController.getOurConversationOrThrow();
|
||||||
|
|
||||||
|
if (newValue === PhoneNumberSharingMode.Everybody) {
|
||||||
|
onWhoCanFindMeChange(PhoneNumberDiscoverability.Discoverable);
|
||||||
|
}
|
||||||
|
account.captureChange('phoneNumberSharingMode');
|
||||||
|
|
||||||
|
// Write profile after updating storage so that the write has up-to-date
|
||||||
|
// information.
|
||||||
|
await writeProfile(getConversation(account), {
|
||||||
|
keepAvatar: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StrictMode>
|
||||||
|
<Preferences
|
||||||
|
addCustomColor={addCustomColor}
|
||||||
|
autoDownloadAttachment={autoDownloadAttachment}
|
||||||
|
availableCameras={availableCameras}
|
||||||
|
availableLocales={availableLocales}
|
||||||
|
availableMicrophones={availableMicrophones}
|
||||||
|
availableSpeakers={availableSpeakers}
|
||||||
|
backupFeatureEnabled={backupFeatureEnabled}
|
||||||
|
backupSubscriptionStatus={backupSubscriptionStatus}
|
||||||
|
blockedCount={blockedCount}
|
||||||
|
cloudBackupStatus={cloudBackupStatus}
|
||||||
|
customColors={customColors}
|
||||||
|
defaultConversationColor={defaultConversationColor}
|
||||||
|
deviceName={deviceName}
|
||||||
|
emojiSkinToneDefault={emojiSkinToneDefault}
|
||||||
|
exportLocalBackup={exportLocalBackup}
|
||||||
|
phoneNumber={phoneNumber}
|
||||||
|
doDeleteAllData={doDeleteAllData}
|
||||||
|
editCustomColor={editCustomColor}
|
||||||
|
getConversationsWithCustomColor={getConversationsWithCustomColor}
|
||||||
|
hasAudioNotifications={hasAudioNotifications}
|
||||||
|
hasAutoConvertEmoji={hasAutoConvertEmoji}
|
||||||
|
hasAutoDownloadUpdate={hasAutoDownloadUpdate}
|
||||||
|
hasAutoLaunch={hasAutoLaunch}
|
||||||
|
hasCallNotifications={hasCallNotifications}
|
||||||
|
hasCallRingtoneNotification={hasCallRingtoneNotification}
|
||||||
|
hasContentProtection={hasContentProtection}
|
||||||
|
hasCountMutedConversations={hasCountMutedConversations}
|
||||||
|
hasHideMenuBar={hasHideMenuBar}
|
||||||
|
hasIncomingCallNotifications={hasIncomingCallNotifications}
|
||||||
|
hasLinkPreviews={hasLinkPreviews}
|
||||||
|
hasMediaCameraPermissions={hasMediaCameraPermissions}
|
||||||
|
hasMediaPermissions={hasMediaPermissions}
|
||||||
|
hasMessageAudio={hasMessageAudio}
|
||||||
|
hasMinimizeToAndStartInSystemTray={hasMinimizeToAndStartInSystemTray}
|
||||||
|
hasMinimizeToSystemTray={hasMinimizeToSystemTray}
|
||||||
|
hasNotificationAttention={hasNotificationAttention}
|
||||||
|
hasNotifications={hasNotifications}
|
||||||
|
hasPendingUpdate={hasPendingUpdate}
|
||||||
|
hasReadReceipts={hasReadReceipts}
|
||||||
|
hasRelayCalls={hasRelayCalls}
|
||||||
|
hasSpellCheck={hasSpellCheck}
|
||||||
|
hasStoriesDisabled={hasStoriesDisabled}
|
||||||
|
hasTextFormatting={hasTextFormatting}
|
||||||
|
hasTypingIndicators={hasTypingIndicators}
|
||||||
|
i18n={i18n}
|
||||||
|
importLocalBackup={importLocalBackup}
|
||||||
|
initialSpellCheckSetting={initialSpellCheckSetting}
|
||||||
|
isAutoDownloadUpdatesSupported={isAutoDownloadUpdatesSupported}
|
||||||
|
isAutoLaunchSupported={isAutoLaunchSupported}
|
||||||
|
isContentProtectionNeeded={isContentProtectionNeeded}
|
||||||
|
isContentProtectionSupported={isContentProtectionSupported}
|
||||||
|
isHideMenuBarSupported={isHideMenuBarSupported}
|
||||||
|
isMinimizeToAndStartInSystemTraySupported={
|
||||||
|
isMinimizeToAndStartInSystemTraySupported
|
||||||
|
}
|
||||||
|
isNotificationAttentionSupported={isNotificationAttentionSupported}
|
||||||
|
isSyncSupported={isSyncSupported}
|
||||||
|
isSystemTraySupported={isSystemTraySupported}
|
||||||
|
isInternalUser={isInternalUser}
|
||||||
|
isUpdateDownloaded={isUpdateDownloaded}
|
||||||
|
lastSyncTime={lastSyncTime}
|
||||||
|
localeOverride={localeOverride}
|
||||||
|
makeSyncRequest={makeSyncRequest}
|
||||||
|
notificationContent={notificationContent}
|
||||||
|
onAudioNotificationsChange={onAudioNotificationsChange}
|
||||||
|
onAutoConvertEmojiChange={onAutoConvertEmojiChange}
|
||||||
|
onAutoDownloadAttachmentChange={onAutoDownloadAttachmentChange}
|
||||||
|
onAutoDownloadUpdateChange={onAutoDownloadUpdateChange}
|
||||||
|
onAutoLaunchChange={onAutoLaunchChange}
|
||||||
|
onCallNotificationsChange={onCallNotificationsChange}
|
||||||
|
onCallRingtoneNotificationChange={onCallRingtoneNotificationChange}
|
||||||
|
onContentProtectionChange={onContentProtectionChange}
|
||||||
|
onCountMutedConversationsChange={onCountMutedConversationsChange}
|
||||||
|
onEmojiSkinToneDefaultChange={onEmojiSkinToneDefaultChange}
|
||||||
|
onHasStoriesDisabledChanged={onHasStoriesDisabledChanged}
|
||||||
|
onHideMenuBarChange={onHideMenuBarChange}
|
||||||
|
onIncomingCallNotificationsChange={onIncomingCallNotificationsChange}
|
||||||
|
onLastSyncTimeChange={onLastSyncTimeChange}
|
||||||
|
onLocaleChange={onLocaleChange}
|
||||||
|
onMediaCameraPermissionsChange={onMediaCameraPermissionsChange}
|
||||||
|
onMediaPermissionsChange={onMediaPermissionsChange}
|
||||||
|
onMessageAudioChange={onMessageAudioChange}
|
||||||
|
onMinimizeToAndStartInSystemTrayChange={
|
||||||
|
onMinimizeToAndStartInSystemTrayChange
|
||||||
|
}
|
||||||
|
onMinimizeToSystemTrayChange={onMinimizeToSystemTrayChange}
|
||||||
|
onNotificationAttentionChange={onNotificationAttentionChange}
|
||||||
|
onNotificationContentChange={onNotificationContentChange}
|
||||||
|
onNotificationsChange={onNotificationsChange}
|
||||||
|
onStartUpdate={startUpdate}
|
||||||
|
onRelayCallsChange={onRelayCallsChange}
|
||||||
|
onSelectedCameraChange={onSelectedCameraChange}
|
||||||
|
onSelectedMicrophoneChange={onSelectedMicrophoneChange}
|
||||||
|
onSelectedSpeakerChange={onSelectedSpeakerChange}
|
||||||
|
onSentMediaQualityChange={onSentMediaQualityChange}
|
||||||
|
onSpellCheckChange={onSpellCheckChange}
|
||||||
|
onTextFormattingChange={onTextFormattingChange}
|
||||||
|
onThemeChange={onThemeChange}
|
||||||
|
onUniversalExpireTimerChange={onUniversalExpireTimerChange}
|
||||||
|
onWhoCanFindMeChange={onWhoCanFindMeChange}
|
||||||
|
onWhoCanSeeMeChange={onWhoCanSeeMeChange}
|
||||||
|
onZoomFactorChange={onZoomFactorChange}
|
||||||
|
preferredSystemLocales={preferredSystemLocales}
|
||||||
|
refreshCloudBackupStatus={refreshCloudBackupStatus}
|
||||||
|
refreshBackupSubscriptionStatus={refreshBackupSubscriptionStatus}
|
||||||
|
removeCustomColorOnConversations={removeCustomColorOnConversations}
|
||||||
|
removeCustomColor={removeCustomColor}
|
||||||
|
renderUpdateDialog={renderUpdateDialog}
|
||||||
|
resetAllChatColors={resetAllChatColors}
|
||||||
|
resetDefaultChatColor={resetDefaultChatColor}
|
||||||
|
resolvedLocale={resolvedLocale}
|
||||||
|
selectedCamera={selectedCamera}
|
||||||
|
selectedMicrophone={selectedMicrophone}
|
||||||
|
selectedSpeaker={selectedSpeaker}
|
||||||
|
sentMediaQualitySetting={sentMediaQualitySetting}
|
||||||
|
setGlobalDefaultConversationColor={setGlobalDefaultConversationColor}
|
||||||
|
themeSetting={themeSetting}
|
||||||
|
universalExpireTimer={universalExpireTimer}
|
||||||
|
validateBackup={validateBackup}
|
||||||
|
whoCanFindMe={whoCanFindMe}
|
||||||
|
whoCanSeeMe={whoCanSeeMe}
|
||||||
|
zoomFactor={zoomFactor}
|
||||||
|
/>
|
||||||
|
</StrictMode>
|
||||||
|
);
|
||||||
|
}
|
|
@ -16,10 +16,12 @@ import {
|
||||||
|
|
||||||
type SmartUpdateDialogProps = Readonly<{
|
type SmartUpdateDialogProps = Readonly<{
|
||||||
containerWidthBreakpoint: WidthBreakpoint;
|
containerWidthBreakpoint: WidthBreakpoint;
|
||||||
|
disableDismiss?: boolean;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export const SmartUpdateDialog = memo(function SmartUpdateDialog({
|
export const SmartUpdateDialog = memo(function SmartUpdateDialog({
|
||||||
containerWidthBreakpoint,
|
containerWidthBreakpoint,
|
||||||
|
disableDismiss,
|
||||||
}: SmartUpdateDialogProps) {
|
}: SmartUpdateDialogProps) {
|
||||||
const i18n = useSelector(getIntl);
|
const i18n = useSelector(getIntl);
|
||||||
const { dismissDialog, snoozeUpdate, startUpdate } = useUpdatesActions();
|
const { dismissDialog, snoozeUpdate, startUpdate } = useUpdatesActions();
|
||||||
|
@ -37,6 +39,7 @@ export const SmartUpdateDialog = memo(function SmartUpdateDialog({
|
||||||
version={version}
|
version={version}
|
||||||
currentVersion={window.getVersion()}
|
currentVersion={window.getVersion()}
|
||||||
dismissDialog={dismissDialog}
|
dismissDialog={dismissDialog}
|
||||||
|
disableDismiss={disableDismiss}
|
||||||
snoozeUpdate={snoozeUpdate}
|
snoozeUpdate={snoozeUpdate}
|
||||||
startUpdate={startUpdate}
|
startUpdate={startUpdate}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -21,6 +21,7 @@ import type { actions as items } from './ducks/items';
|
||||||
import type { actions as lightbox } from './ducks/lightbox';
|
import type { actions as lightbox } from './ducks/lightbox';
|
||||||
import type { actions as linkPreviews } from './ducks/linkPreviews';
|
import type { actions as linkPreviews } from './ducks/linkPreviews';
|
||||||
import type { actions as mediaGallery } from './ducks/mediaGallery';
|
import type { actions as mediaGallery } from './ducks/mediaGallery';
|
||||||
|
import type { actions as nav } from './ducks/nav';
|
||||||
import type { actions as network } from './ducks/network';
|
import type { actions as network } from './ducks/network';
|
||||||
import type { actions as notificationProfiles } from './ducks/notificationProfiles';
|
import type { actions as notificationProfiles } from './ducks/notificationProfiles';
|
||||||
import type { actions as safetyNumber } from './ducks/safetyNumber';
|
import type { actions as safetyNumber } from './ducks/safetyNumber';
|
||||||
|
@ -54,6 +55,7 @@ export type ReduxActions = {
|
||||||
lightbox: typeof lightbox;
|
lightbox: typeof lightbox;
|
||||||
linkPreviews: typeof linkPreviews;
|
linkPreviews: typeof linkPreviews;
|
||||||
mediaGallery: typeof mediaGallery;
|
mediaGallery: typeof mediaGallery;
|
||||||
|
nav: typeof nav;
|
||||||
network: typeof network;
|
network: typeof network;
|
||||||
notificationProfiles: typeof notificationProfiles;
|
notificationProfiles: typeof notificationProfiles;
|
||||||
safetyNumber: typeof safetyNumber;
|
safetyNumber: typeof safetyNumber;
|
||||||
|
|
|
@ -278,11 +278,4 @@ export async function setupBasics(): Promise<void> {
|
||||||
systemGivenName: 'ME',
|
systemGivenName: 'ME',
|
||||||
profileKey: Bytes.toBase64(PROFILE_KEY),
|
profileKey: Bytes.toBase64(PROFILE_KEY),
|
||||||
});
|
});
|
||||||
|
|
||||||
window.Events = {
|
|
||||||
...window.Events,
|
|
||||||
getTypingIndicatorSetting: () =>
|
|
||||||
window.storage.get('typingIndicators', false),
|
|
||||||
getLinkPreviewSetting: () => window.storage.get('linkPreviews', false),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -230,24 +230,12 @@ describe('calling duck', () => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
let oldEvents: any;
|
|
||||||
beforeEach(function (this: Mocha.Context) {
|
beforeEach(function (this: Mocha.Context) {
|
||||||
this.sandbox = sinon.createSandbox();
|
this.sandbox = sinon.createSandbox();
|
||||||
|
|
||||||
oldEvents = window.Events;
|
|
||||||
window.Events = {
|
|
||||||
...(oldEvents || {}),
|
|
||||||
|
|
||||||
getCallRingtoneNotification: sinon.spy(),
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
} as any;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function (this: Mocha.Context) {
|
afterEach(function (this: Mocha.Context) {
|
||||||
this.sandbox.restore();
|
this.sandbox.restore();
|
||||||
|
|
||||||
window.Events = oldEvents;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('actions', () => {
|
describe('actions', () => {
|
||||||
|
|
|
@ -31,30 +31,31 @@ describe('settings', function (this: Mocha.Suite) {
|
||||||
await bootstrap.teardown();
|
await bootstrap.teardown();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('settings window and all panes load when opened', async () => {
|
it('settings tab and all panes load when opened', async () => {
|
||||||
const window = await app.getWindow();
|
const window = await app.getWindow();
|
||||||
|
|
||||||
const newPagePromise = window.context().waitForEvent('page');
|
|
||||||
await window.locator('.NavTabs__ItemIcon--Settings').click();
|
await window.locator('.NavTabs__ItemIcon--Settings').click();
|
||||||
const settingsWindow = await newPagePromise;
|
await window.getByRole('heading', { name: 'Settings' }).waitFor();
|
||||||
await settingsWindow.getByText('Device Name').waitFor();
|
|
||||||
|
|
||||||
await settingsWindow.getByText('Appearance').click();
|
await window.getByRole('button', { name: 'General' }).click();
|
||||||
await settingsWindow.getByText('Language').first().waitFor();
|
await window.getByText('Device Name').waitFor();
|
||||||
|
|
||||||
await settingsWindow.getByText('Chats').click();
|
await window.getByRole('button', { name: 'Appearance' }).click();
|
||||||
await settingsWindow.getByText('Spell check text').waitFor();
|
await window.getByText('Language').first().waitFor();
|
||||||
|
|
||||||
await settingsWindow.getByText('Calls').click();
|
await window.getByRole('button', { name: 'Chats' }).click();
|
||||||
await settingsWindow.getByText('Enable incoming calls').waitFor();
|
await window.getByText('Spell check text').waitFor();
|
||||||
|
|
||||||
await settingsWindow.getByText('Notifications').click();
|
await window.getByRole('button', { name: 'Calls' }).click();
|
||||||
await settingsWindow.getByText('Notification content').waitFor();
|
await window.getByText('Enable incoming calls').waitFor();
|
||||||
|
|
||||||
await settingsWindow.getByText('Privacy').click();
|
await window.getByRole('button', { name: 'Notifications' }).click();
|
||||||
await settingsWindow.getByText('Read receipts').waitFor();
|
await window.getByText('Notification content').waitFor();
|
||||||
|
|
||||||
await settingsWindow.getByText('Data usage').click();
|
await window.getByRole('button', { name: 'Privacy' }).click();
|
||||||
await settingsWindow.getByText('Sent media quality').waitFor();
|
await window.getByText('Read receipts').waitFor();
|
||||||
|
|
||||||
|
await window.getByRole('button', { name: 'Data usage' }).click();
|
||||||
|
await window.getByText('Sent media quality').waitFor();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -50,6 +50,10 @@ export type AddLinkPreviewOptionsType = Readonly<{
|
||||||
|
|
||||||
const linkify = new LinkifyIt();
|
const linkify = new LinkifyIt();
|
||||||
|
|
||||||
|
export function getLinkPreviewSetting(): boolean {
|
||||||
|
return window.storage.get('linkPreviews', false);
|
||||||
|
}
|
||||||
|
|
||||||
export function isValidLink(maybeUrl: string | undefined): boolean {
|
export function isValidLink(maybeUrl: string | undefined): boolean {
|
||||||
if (maybeUrl == null) {
|
if (maybeUrl == null) {
|
||||||
return false;
|
return false;
|
||||||
|
|
8
ts/types/Storage.d.ts
vendored
8
ts/types/Storage.d.ts
vendored
|
@ -122,7 +122,7 @@ export type StorageAccessType = {
|
||||||
signedKeyUpdateTime: number;
|
signedKeyUpdateTime: number;
|
||||||
signedKeyUpdateTimePNI: number;
|
signedKeyUpdateTimePNI: number;
|
||||||
storageKey: string;
|
storageKey: string;
|
||||||
synced_at: number;
|
synced_at: number | undefined;
|
||||||
userAgent: string;
|
userAgent: string;
|
||||||
uuid_id: string;
|
uuid_id: string;
|
||||||
useRingrtcAdm: boolean;
|
useRingrtcAdm: boolean;
|
||||||
|
@ -148,9 +148,9 @@ export type StorageAccessType = {
|
||||||
'storage-service-error-records': ReadonlyArray<UnknownRecord>;
|
'storage-service-error-records': ReadonlyArray<UnknownRecord>;
|
||||||
'storage-service-unknown-records': ReadonlyArray<UnknownRecord>;
|
'storage-service-unknown-records': ReadonlyArray<UnknownRecord>;
|
||||||
'storage-service-pending-deletes': ReadonlyArray<ExtendedStorageID>;
|
'storage-service-pending-deletes': ReadonlyArray<ExtendedStorageID>;
|
||||||
'preferred-video-input-device': string;
|
'preferred-video-input-device': string | undefined;
|
||||||
'preferred-audio-input-device': AudioDevice;
|
'preferred-audio-input-device': AudioDevice | undefined;
|
||||||
'preferred-audio-output-device': AudioDevice;
|
'preferred-audio-output-device': AudioDevice | undefined;
|
||||||
remoteConfig: RemoteConfigType;
|
remoteConfig: RemoteConfigType;
|
||||||
serverTimeSkew: number;
|
serverTimeSkew: number;
|
||||||
unidentifiedDeliveryIndicators: boolean;
|
unidentifiedDeliveryIndicators: boolean;
|
||||||
|
|
|
@ -179,3 +179,19 @@ export type StoryMessageRecipientsType = Array<{
|
||||||
distributionListIds: Array<StoryDistributionIdString>;
|
distributionListIds: Array<StoryDistributionIdString>;
|
||||||
isAllowedToReply: boolean;
|
isAllowedToReply: boolean;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
export function areStoryViewReceiptsEnabled(): boolean {
|
||||||
|
return (
|
||||||
|
window.storage.get('storyViewReceiptsEnabled') ??
|
||||||
|
window.storage.get('read-receipt-setting') ??
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setStoryViewReceiptsEnabled(
|
||||||
|
value: boolean
|
||||||
|
): Promise<void> {
|
||||||
|
await window.storage.put('storyViewReceiptsEnabled', value);
|
||||||
|
const account = window.ConversationController.getOurConversationOrThrow();
|
||||||
|
account.captureChange('storyViewReceiptsEnabled');
|
||||||
|
}
|
||||||
|
|
|
@ -112,3 +112,13 @@ export type JSONWithUnknownFields<Value> =
|
||||||
|
|
||||||
export type WithRequiredProperties<T, P extends keyof T> = Omit<T, P> &
|
export type WithRequiredProperties<T, P extends keyof T> = Omit<T, P> &
|
||||||
Required<Pick<T, P>>;
|
Required<Pick<T, P>>;
|
||||||
|
|
||||||
|
export function getTypingIndicatorSetting(): boolean {
|
||||||
|
return window.storage.get('typingIndicators', false);
|
||||||
|
}
|
||||||
|
export function getReadReceiptSetting(): boolean {
|
||||||
|
return window.storage.get('read-receipt-setting', false);
|
||||||
|
}
|
||||||
|
export function getSealedSenderIndicatorSetting(): boolean {
|
||||||
|
return window.storage.get('sealedSenderIndicators', false);
|
||||||
|
}
|
||||||
|
|
|
@ -34,18 +34,15 @@ import {
|
||||||
isNotUpdatable,
|
isNotUpdatable,
|
||||||
isStaging,
|
isStaging,
|
||||||
} from '../util/version';
|
} from '../util/version';
|
||||||
|
import { isPathInside } from '../util/isPathInside';
|
||||||
|
|
||||||
import * as packageJson from '../../package.json';
|
import * as packageJson from '../../package.json';
|
||||||
import type { SettingsChannel } from '../main/settingsChannel';
|
|
||||||
import { isPathInside } from '../util/isPathInside';
|
|
||||||
import {
|
import {
|
||||||
getSignatureFileName,
|
getSignatureFileName,
|
||||||
hexToBinary,
|
hexToBinary,
|
||||||
verifySignature,
|
verifySignature,
|
||||||
} from './signature';
|
} from './signature';
|
||||||
|
|
||||||
import type { LoggerType } from '../types/Logging';
|
|
||||||
import type { PrepareDownloadResultType as DifferentialDownloadDataType } from './differential';
|
|
||||||
import {
|
import {
|
||||||
download as downloadDifferentialData,
|
download as downloadDifferentialData,
|
||||||
getBlockMapFileName,
|
getBlockMapFileName,
|
||||||
|
@ -60,6 +57,10 @@ import {
|
||||||
isTimeToUpdate,
|
isTimeToUpdate,
|
||||||
} from './util';
|
} from './util';
|
||||||
|
|
||||||
|
import type { LoggerType } from '../types/Logging';
|
||||||
|
import type { PrepareDownloadResultType as DifferentialDownloadDataType } from './differential';
|
||||||
|
import type { MainSQL } from '../sql/main';
|
||||||
|
|
||||||
const POLL_INTERVAL = 30 * durations.MINUTE;
|
const POLL_INTERVAL = 30 * durations.MINUTE;
|
||||||
|
|
||||||
type JSONVendorSchema = {
|
type JSONVendorSchema = {
|
||||||
|
@ -109,10 +110,10 @@ type DownloadUpdateResultType = Readonly<{
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type UpdaterOptionsType = Readonly<{
|
export type UpdaterOptionsType = Readonly<{
|
||||||
settingsChannel: SettingsChannel;
|
|
||||||
logger: LoggerType;
|
|
||||||
getMainWindow: () => BrowserWindow | undefined;
|
|
||||||
canRunSilently: () => boolean;
|
canRunSilently: () => boolean;
|
||||||
|
getMainWindow: () => BrowserWindow | undefined;
|
||||||
|
logger: LoggerType;
|
||||||
|
sql: MainSQL;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
enum CheckType {
|
enum CheckType {
|
||||||
|
@ -134,7 +135,7 @@ export abstract class Updater {
|
||||||
|
|
||||||
protected readonly logger: LoggerType;
|
protected readonly logger: LoggerType;
|
||||||
|
|
||||||
readonly #settingsChannel: SettingsChannel;
|
readonly #sql: MainSQL;
|
||||||
|
|
||||||
protected readonly getMainWindow: () => BrowserWindow | undefined;
|
protected readonly getMainWindow: () => BrowserWindow | undefined;
|
||||||
|
|
||||||
|
@ -157,15 +158,15 @@ export abstract class Updater {
|
||||||
#pollId = getGuid();
|
#pollId = getGuid();
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
settingsChannel,
|
|
||||||
logger,
|
|
||||||
getMainWindow,
|
|
||||||
canRunSilently,
|
canRunSilently,
|
||||||
|
getMainWindow,
|
||||||
|
logger,
|
||||||
|
sql,
|
||||||
}: UpdaterOptionsType) {
|
}: UpdaterOptionsType) {
|
||||||
this.#settingsChannel = settingsChannel;
|
|
||||||
this.logger = logger;
|
|
||||||
this.getMainWindow = getMainWindow;
|
|
||||||
this.#canRunSilently = canRunSilently;
|
this.#canRunSilently = canRunSilently;
|
||||||
|
this.getMainWindow = getMainWindow;
|
||||||
|
this.logger = logger;
|
||||||
|
this.#sql = sql;
|
||||||
|
|
||||||
this.#throttledSendDownloadingUpdate = throttle(
|
this.#throttledSendDownloadingUpdate = throttle(
|
||||||
(downloadedSize: number, downloadSize: number) => {
|
(downloadedSize: number, downloadSize: number) => {
|
||||||
|
@ -921,9 +922,11 @@ export abstract class Updater {
|
||||||
|
|
||||||
async #getAutoDownloadUpdateSetting(): Promise<boolean> {
|
async #getAutoDownloadUpdateSetting(): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
return await this.#settingsChannel.getSettingFromMainWindow(
|
const result = await this.#sql.sqlRead(
|
||||||
'autoDownloadUpdate'
|
'getItemById',
|
||||||
|
'auto-download-update'
|
||||||
);
|
);
|
||||||
|
return result?.value ?? true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.warn(
|
this.logger.warn(
|
||||||
'getAutoDownloadUpdateSetting: Failed to fetch, returning false',
|
'getAutoDownloadUpdateSetting: Failed to fetch, returning false',
|
||||||
|
|
|
@ -11,11 +11,15 @@ const ringtoneEventQueue = new PQueue({
|
||||||
throwOnTimeout: true,
|
throwOnTimeout: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function getCallRingtoneNotificationSetting(): boolean {
|
||||||
|
return window.storage.get('call-ringtone-notification', true);
|
||||||
|
}
|
||||||
|
|
||||||
class CallingTones {
|
class CallingTones {
|
||||||
#ringtone?: Sound;
|
#ringtone?: Sound;
|
||||||
|
|
||||||
async handRaised() {
|
async handRaised() {
|
||||||
const canPlayTone = window.Events.getCallRingtoneNotification();
|
const canPlayTone = getCallRingtoneNotificationSetting();
|
||||||
if (!canPlayTone) {
|
if (!canPlayTone) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -28,7 +32,7 @@ class CallingTones {
|
||||||
}
|
}
|
||||||
|
|
||||||
async playEndCall(): Promise<void> {
|
async playEndCall(): Promise<void> {
|
||||||
const canPlayTone = window.Events.getCallRingtoneNotification();
|
const canPlayTone = getCallRingtoneNotificationSetting();
|
||||||
if (!canPlayTone) {
|
if (!canPlayTone) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -46,7 +50,7 @@ class CallingTones {
|
||||||
this.#ringtone = undefined;
|
this.#ringtone = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const canPlayTone = window.Events.getCallRingtoneNotification();
|
const canPlayTone = getCallRingtoneNotificationSetting();
|
||||||
if (!canPlayTone) {
|
if (!canPlayTone) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -70,7 +74,7 @@ class CallingTones {
|
||||||
}
|
}
|
||||||
|
|
||||||
async someonePresenting() {
|
async someonePresenting() {
|
||||||
const canPlayTone = window.Events.getCallRingtoneNotification();
|
const canPlayTone = getCallRingtoneNotificationSetting();
|
||||||
if (!canPlayTone) {
|
if (!canPlayTone) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,46 +3,20 @@
|
||||||
|
|
||||||
import { ipcRenderer } from 'electron';
|
import { ipcRenderer } from 'electron';
|
||||||
import type { SystemPreferences } from 'electron';
|
import type { SystemPreferences } from 'electron';
|
||||||
import type { AudioDevice } from '@signalapp/ringrtc';
|
|
||||||
import { noop } from 'lodash';
|
import { noop } from 'lodash';
|
||||||
|
|
||||||
import type {
|
import type { ZoomFactorType } from '../types/Storage.d';
|
||||||
AutoDownloadAttachmentType,
|
|
||||||
ZoomFactorType,
|
|
||||||
} from '../types/Storage.d';
|
|
||||||
import type {
|
|
||||||
ConversationColorType,
|
|
||||||
CustomColorType,
|
|
||||||
DefaultConversationColorType,
|
|
||||||
} from '../types/Colors';
|
|
||||||
import { DEFAULT_CONVERSATION_COLOR } from '../types/Colors';
|
|
||||||
import * as Errors from '../types/errors';
|
import * as Errors from '../types/errors';
|
||||||
import * as Stickers from '../types/Stickers';
|
import * as Stickers from '../types/Stickers';
|
||||||
|
import * as Settings from '../types/Settings';
|
||||||
|
|
||||||
import type { ConversationType } from '../state/ducks/conversations';
|
|
||||||
import { calling } from '../services/calling';
|
|
||||||
import { resolveUsernameByLinkBase64 } from '../services/username';
|
import { resolveUsernameByLinkBase64 } from '../services/username';
|
||||||
import { writeProfile } from '../services/writeProfile';
|
|
||||||
import {
|
|
||||||
backupsService,
|
|
||||||
type ValidationResultType as BackupValidationResultType,
|
|
||||||
} from '../services/backups';
|
|
||||||
import { isInCall } from '../state/selectors/calling';
|
import { isInCall } from '../state/selectors/calling';
|
||||||
import { getConversationsWithCustomColorSelector } from '../state/selectors/conversations';
|
|
||||||
import { getCustomColors } from '../state/selectors/items';
|
|
||||||
import { themeChanged } from '../shims/themeChanged';
|
|
||||||
import { renderClearingDataView } from '../shims/renderClearingDataView';
|
|
||||||
|
|
||||||
import * as universalExpireTimer from './universalExpireTimer';
|
import { strictAssert } from './assert';
|
||||||
import { PhoneNumberDiscoverability } from './phoneNumberDiscoverability';
|
|
||||||
import { PhoneNumberSharingMode } from './phoneNumberSharingMode';
|
|
||||||
import { strictAssert, assertDev } from './assert';
|
|
||||||
import * as durations from './durations';
|
|
||||||
import type { DurationInSeconds } from './durations';
|
|
||||||
import * as Registration from './registration';
|
import * as Registration from './registration';
|
||||||
import { lookupConversationWithoutServiceId } from './lookupConversationWithoutServiceId';
|
import { lookupConversationWithoutServiceId } from './lookupConversationWithoutServiceId';
|
||||||
import * as log from '../logging/log';
|
import * as log from '../logging/log';
|
||||||
import { deleteAllMyStories } from './deleteAllMyStories';
|
|
||||||
import {
|
import {
|
||||||
type NotificationClickData,
|
type NotificationClickData,
|
||||||
notificationService,
|
notificationService,
|
||||||
|
@ -50,103 +24,34 @@ import {
|
||||||
import { StoryViewModeType, StoryViewTargetType } from '../types/Stories';
|
import { StoryViewModeType, StoryViewTargetType } from '../types/Stories';
|
||||||
import { isValidE164 } from './isValidE164';
|
import { isValidE164 } from './isValidE164';
|
||||||
import { fromWebSafeBase64 } from './webSafeBase64';
|
import { fromWebSafeBase64 } from './webSafeBase64';
|
||||||
import { getConversation } from './getConversation';
|
|
||||||
import { instance, PhoneNumberFormat } from './libphonenumberInstance';
|
|
||||||
import { showConfirmationDialog } from './showConfirmationDialog';
|
import { showConfirmationDialog } from './showConfirmationDialog';
|
||||||
import type {
|
import type {
|
||||||
EphemeralSettings,
|
EphemeralSettings,
|
||||||
SettingsValuesType,
|
SettingsValuesType,
|
||||||
ThemeType,
|
ThemeType,
|
||||||
} from './preload';
|
} from './preload';
|
||||||
import type { SystemTraySetting } from '../types/SystemTraySetting';
|
import { SystemTraySetting } from '../types/SystemTraySetting';
|
||||||
import { drop } from './drop';
|
import OS from './os/osPreload';
|
||||||
import { sendSyncRequests } from '../textsecure/syncRequests';
|
|
||||||
import { waitForEvent } from '../shims/events';
|
|
||||||
import { DEFAULT_AUTO_DOWNLOAD_ATTACHMENT } from '../textsecure/Storage';
|
|
||||||
import { EmojiSkinTone } from '../components/fun/data/emojis';
|
|
||||||
import type {
|
|
||||||
BackupsSubscriptionType,
|
|
||||||
BackupStatusType,
|
|
||||||
} from '../types/backups';
|
|
||||||
import { isBackupFeatureEnabled } from './isBackupEnabled';
|
|
||||||
import { isSettingsInternalEnabled } from './isSettingsInternalEnabled';
|
|
||||||
import type { ValidateLocalBackupStructureResultType } from '../services/backups/util/localBackup';
|
|
||||||
|
|
||||||
type SentMediaQualityType = 'standard' | 'high';
|
|
||||||
type NotificationSettingType = 'message' | 'name' | 'count' | 'off';
|
|
||||||
|
|
||||||
export type IPCEventsValuesType = {
|
export type IPCEventsValuesType = {
|
||||||
alwaysRelayCalls: boolean | undefined;
|
// IPC-mediated
|
||||||
audioNotification: boolean | undefined;
|
|
||||||
audioMessage: boolean;
|
|
||||||
autoConvertEmoji: boolean;
|
|
||||||
autoDownloadAttachment: AutoDownloadAttachmentType;
|
|
||||||
autoDownloadUpdate: boolean;
|
|
||||||
autoLaunch: boolean;
|
autoLaunch: boolean;
|
||||||
callRingtoneNotification: boolean;
|
|
||||||
callSystemNotification: boolean;
|
|
||||||
countMutedConversations: boolean;
|
|
||||||
hasStoriesDisabled: boolean;
|
|
||||||
hideMenuBar: boolean | undefined;
|
|
||||||
incomingCallNotification: boolean;
|
|
||||||
lastSyncTime: number | undefined;
|
|
||||||
notificationDrawAttention: boolean;
|
|
||||||
notificationSetting: NotificationSettingType;
|
|
||||||
preferredAudioInputDevice: AudioDevice | undefined;
|
|
||||||
preferredAudioOutputDevice: AudioDevice | undefined;
|
|
||||||
preferredVideoInputDevice: string | undefined;
|
|
||||||
sentMediaQualitySetting: SentMediaQualityType;
|
|
||||||
textFormatting: boolean;
|
|
||||||
universalExpireTimer: DurationInSeconds;
|
|
||||||
zoomFactor: ZoomFactorType;
|
|
||||||
storyViewReceiptsEnabled: boolean;
|
|
||||||
|
|
||||||
// Optional
|
|
||||||
mediaPermissions: boolean;
|
mediaPermissions: boolean;
|
||||||
mediaCameraPermissions: boolean | undefined;
|
mediaCameraPermissions: boolean | undefined;
|
||||||
|
zoomFactor: ZoomFactorType;
|
||||||
// Only getters
|
|
||||||
backupFeatureEnabled: boolean;
|
|
||||||
cloudBackupStatus: BackupStatusType | undefined;
|
|
||||||
backupSubscriptionStatus: BackupsSubscriptionType | undefined;
|
|
||||||
blockedCount: number;
|
|
||||||
linkPreviewSetting: boolean;
|
|
||||||
phoneNumberDiscoverabilitySetting: PhoneNumberDiscoverability;
|
|
||||||
phoneNumberSharingSetting: PhoneNumberSharingMode;
|
|
||||||
readReceiptSetting: boolean;
|
|
||||||
typingIndicatorSetting: boolean;
|
|
||||||
deviceName: string | undefined;
|
|
||||||
phoneNumber: string | undefined;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type IPCEventsCallbacksType = {
|
export type IPCEventsCallbacksType = {
|
||||||
getAvailableIODevices(): Promise<{
|
|
||||||
availableCameras: Array<
|
|
||||||
Pick<MediaDeviceInfo, 'deviceId' | 'groupId' | 'kind' | 'label'>
|
|
||||||
>;
|
|
||||||
availableMicrophones: Array<AudioDevice>;
|
|
||||||
availableSpeakers: Array<AudioDevice>;
|
|
||||||
}>;
|
|
||||||
refreshCloudBackupStatus(): void;
|
|
||||||
refreshBackupSubscriptionStatus(): void;
|
|
||||||
addCustomColor: (customColor: CustomColorType) => void;
|
|
||||||
addDarkOverlay: () => void;
|
addDarkOverlay: () => void;
|
||||||
|
removeDarkOverlay: () => void;
|
||||||
|
|
||||||
cleanupDownloads: () => Promise<void>;
|
cleanupDownloads: () => Promise<void>;
|
||||||
deleteAllData: () => Promise<void>;
|
getIsInCall: () => boolean;
|
||||||
deleteAllMyStories: () => Promise<void>;
|
|
||||||
editCustomColor: (colorId: string, customColor: CustomColorType) => void;
|
|
||||||
getConversationsWithCustomColor: (x: string) => Array<ConversationType>;
|
|
||||||
getMediaAccessStatus: (
|
getMediaAccessStatus: (
|
||||||
mediaType: 'screen' | 'microphone' | 'camera'
|
mediaType: 'screen' | 'microphone' | 'camera'
|
||||||
) => Promise<ReturnType<SystemPreferences['getMediaAccessStatus']>>;
|
) => Promise<ReturnType<SystemPreferences['getMediaAccessStatus']>>;
|
||||||
installStickerPack: (packId: string, key: string) => Promise<void>;
|
installStickerPack: (packId: string, key: string) => Promise<void>;
|
||||||
isPrimary: () => boolean;
|
requestCloseConfirmation: () => Promise<boolean>;
|
||||||
isInternalUser: () => boolean;
|
|
||||||
removeCustomColor: (x: string) => void;
|
|
||||||
removeCustomColorOnConversations: (x: string) => void;
|
|
||||||
removeDarkOverlay: () => void;
|
|
||||||
resetAllChatColors: () => void;
|
|
||||||
resetDefaultChatColor: () => void;
|
|
||||||
setMediaPlaybackDisabled: (playbackDisabled: boolean) => void;
|
setMediaPlaybackDisabled: (playbackDisabled: boolean) => void;
|
||||||
showConversationViaNotification: (data: NotificationClickData) => void;
|
showConversationViaNotification: (data: NotificationClickData) => void;
|
||||||
showConversationViaToken: (token: string) => void;
|
showConversationViaToken: (token: string) => void;
|
||||||
|
@ -158,23 +63,9 @@ export type IPCEventsCallbacksType = {
|
||||||
showGroupViaLink: (value: string) => Promise<void>;
|
showGroupViaLink: (value: string) => Promise<void>;
|
||||||
showReleaseNotes: () => void;
|
showReleaseNotes: () => void;
|
||||||
showStickerPack: (packId: string, key: string) => void;
|
showStickerPack: (packId: string, key: string) => void;
|
||||||
startCallingLobbyViaToken: (token: string) => void;
|
|
||||||
requestCloseConfirmation: () => Promise<boolean>;
|
|
||||||
getIsInCall: () => boolean;
|
|
||||||
shutdown: () => Promise<void>;
|
shutdown: () => Promise<void>;
|
||||||
|
startCallingLobbyViaToken: (token: string) => void;
|
||||||
unknownSignalLink: () => void;
|
unknownSignalLink: () => void;
|
||||||
getCustomColors: () => Record<string, CustomColorType>;
|
|
||||||
syncRequest: () => Promise<void>;
|
|
||||||
exportLocalBackup: () => Promise<BackupValidationResultType>;
|
|
||||||
importLocalBackup: () => Promise<ValidateLocalBackupStructureResultType>;
|
|
||||||
validateBackup: () => Promise<BackupValidationResultType>;
|
|
||||||
setGlobalDefaultConversationColor: (
|
|
||||||
color: ConversationColorType,
|
|
||||||
customColor?: { id: string; value: CustomColorType }
|
|
||||||
) => void;
|
|
||||||
setEmojiSkinToneDefault: (emojiSkinTone: EmojiSkinTone) => void;
|
|
||||||
getDefaultConversationColor: () => DefaultConversationColorType;
|
|
||||||
getEmojiSkinToneDefault: () => EmojiSkinTone;
|
|
||||||
uploadStickerPack: (
|
uploadStickerPack: (
|
||||||
manifest: Uint8Array,
|
manifest: Uint8Array,
|
||||||
stickers: ReadonlyArray<Uint8Array>
|
stickers: ReadonlyArray<Uint8Array>
|
||||||
|
@ -183,42 +74,20 @@ export type IPCEventsCallbacksType = {
|
||||||
|
|
||||||
type ValuesWithGetters = Omit<
|
type ValuesWithGetters = Omit<
|
||||||
SettingsValuesType,
|
SettingsValuesType,
|
||||||
// Async
|
// Async - we'll redefine these in IPCEventsGettersType
|
||||||
| 'zoomFactor'
|
| 'autoLaunch'
|
||||||
| 'localeOverride'
|
| 'localeOverride'
|
||||||
| 'spellCheck'
|
|
||||||
| 'themeSetting'
|
|
||||||
// Optional
|
|
||||||
| 'mediaPermissions'
|
| 'mediaPermissions'
|
||||||
| 'mediaCameraPermissions'
|
| 'mediaCameraPermissions'
|
||||||
| 'autoLaunch'
|
|
||||||
| 'spellCheck'
|
| 'spellCheck'
|
||||||
| 'contentProtection'
|
| 'contentProtection'
|
||||||
| 'systemTraySetting'
|
| 'systemTraySetting'
|
||||||
|
| 'themeSetting'
|
||||||
|
| 'zoomFactor'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
type ValuesWithSetters = Omit<
|
// Right now everything is symmetrical
|
||||||
SettingsValuesType,
|
type ValuesWithSetters = SettingsValuesType;
|
||||||
| 'blockedCount'
|
|
||||||
| 'defaultConversationColor'
|
|
||||||
| 'linkPreviewSetting'
|
|
||||||
| 'readReceiptSetting'
|
|
||||||
| 'typingIndicatorSetting'
|
|
||||||
| 'deviceName'
|
|
||||||
| 'phoneNumber'
|
|
||||||
| 'backupFeatureEnabled'
|
|
||||||
| 'cloudBackupStatus'
|
|
||||||
| 'backupSubscriptionStatus'
|
|
||||||
|
|
||||||
// Optional
|
|
||||||
| 'mediaPermissions'
|
|
||||||
| 'mediaCameraPermissions'
|
|
||||||
|
|
||||||
// Only set in the Settings window
|
|
||||||
| 'localeOverride'
|
|
||||||
| 'spellCheck'
|
|
||||||
| 'systemTraySetting'
|
|
||||||
>;
|
|
||||||
|
|
||||||
export type IPCEventsUpdatersType = {
|
export type IPCEventsUpdatersType = {
|
||||||
[Key in keyof EphemeralSettings as IPCEventUpdaterType<Key>]?: (
|
[Key in keyof EphemeralSettings as IPCEventUpdaterType<Key>]?: (
|
||||||
|
@ -235,22 +104,23 @@ export type IPCEventSetterType<Key extends keyof SettingsValuesType> =
|
||||||
export type IPCEventUpdaterType<Key extends keyof SettingsValuesType> =
|
export type IPCEventUpdaterType<Key extends keyof SettingsValuesType> =
|
||||||
`update${Capitalize<Key>}`;
|
`update${Capitalize<Key>}`;
|
||||||
|
|
||||||
|
export type ZoomFactorChangeCallback = (zoomFactor: ZoomFactorType) => void;
|
||||||
export type IPCEventsGettersType = {
|
export type IPCEventsGettersType = {
|
||||||
[Key in keyof ValuesWithGetters as IPCEventGetterType<Key>]: () => ValuesWithGetters[Key];
|
[Key in keyof ValuesWithGetters as IPCEventGetterType<Key>]: () => ValuesWithGetters[Key];
|
||||||
} & {
|
} & {
|
||||||
// Async
|
// Async
|
||||||
getZoomFactor: () => Promise<ZoomFactorType>;
|
getAutoLaunch: () => Promise<boolean>;
|
||||||
getLocaleOverride: () => Promise<string | null>;
|
getLocaleOverride: () => Promise<string | null>;
|
||||||
|
getMediaPermissions: () => Promise<boolean>;
|
||||||
|
getMediaCameraPermissions: () => Promise<boolean>;
|
||||||
getSpellCheck: () => Promise<boolean>;
|
getSpellCheck: () => Promise<boolean>;
|
||||||
getContentProtection: () => Promise<boolean>;
|
getContentProtection: () => Promise<boolean>;
|
||||||
getSystemTraySetting: () => Promise<SystemTraySetting>;
|
getSystemTraySetting: () => Promise<SystemTraySetting>;
|
||||||
getThemeSetting: () => Promise<ThemeType>;
|
getThemeSetting: () => Promise<ThemeType>;
|
||||||
|
getZoomFactor: () => Promise<ZoomFactorType>;
|
||||||
// Events
|
// Events
|
||||||
onZoomFactorChange: (callback: (zoomFactor: ZoomFactorType) => void) => void;
|
onZoomFactorChange: (callback: ZoomFactorChangeCallback) => void;
|
||||||
// Optional
|
offZoomFactorChange: (callback: ZoomFactorChangeCallback) => void;
|
||||||
getMediaPermissions?: () => Promise<boolean>;
|
|
||||||
getMediaCameraPermissions?: () => Promise<boolean>;
|
|
||||||
getAutoLaunch?: () => Promise<boolean>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type IPCEventsSettersType = {
|
export type IPCEventsSettersType = {
|
||||||
|
@ -258,6 +128,7 @@ export type IPCEventsSettersType = {
|
||||||
value: NonNullable<ValuesWithSetters[Key]>
|
value: NonNullable<ValuesWithSetters[Key]>
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
} & {
|
} & {
|
||||||
|
setLocaleOverride: (value: string | null) => Promise<void>;
|
||||||
setMediaPermissions?: (value: boolean) => Promise<void>;
|
setMediaPermissions?: (value: boolean) => Promise<void>;
|
||||||
setMediaCameraPermissions?: (value: boolean) => Promise<void>;
|
setMediaCameraPermissions?: (value: boolean) => Promise<void>;
|
||||||
};
|
};
|
||||||
|
@ -270,325 +141,92 @@ export type IPCEventsType = IPCEventsGettersType &
|
||||||
export function createIPCEvents(
|
export function createIPCEvents(
|
||||||
overrideEvents: Partial<IPCEventsType> = {}
|
overrideEvents: Partial<IPCEventsType> = {}
|
||||||
): IPCEventsType {
|
): IPCEventsType {
|
||||||
const setPhoneNumberDiscoverabilitySetting = async (
|
let zoomFactorChangeCallbacks: Array<ZoomFactorChangeCallback> = [];
|
||||||
newValue: PhoneNumberDiscoverability
|
ipcRenderer.on('zoomFactorChanged', (_event, zoomFactor) => {
|
||||||
): Promise<void> => {
|
zoomFactorChangeCallbacks.forEach(callback => callback(zoomFactor));
|
||||||
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(),
|
// From IPCEventsValuesType
|
||||||
getPhoneNumber: () => {
|
getAutoLaunch: async () => {
|
||||||
try {
|
return (await window.IPC.getAutoLaunch()) ?? false;
|
||||||
const e164 = window.textsecure.storage.user.getNumber();
|
},
|
||||||
const parsedNumber = instance.parse(e164);
|
setAutoLaunch: async (value: boolean) => {
|
||||||
return instance.format(parsedNumber, PhoneNumberFormat.INTERNATIONAL);
|
await window.IPC.setAutoLaunch(value);
|
||||||
} catch (error) {
|
},
|
||||||
log.warn(
|
getMediaCameraPermissions: async () => {
|
||||||
'IPC.getPhoneNumber: failed to parse our E164',
|
return (await window.IPC.getMediaCameraPermissions()) ?? false;
|
||||||
Errors.toLogFormat(error)
|
},
|
||||||
);
|
setMediaCameraPermissions: async () => {
|
||||||
return '';
|
const forCamera = true;
|
||||||
}
|
await window.IPC.showPermissionsPopup(false, forCamera);
|
||||||
|
},
|
||||||
|
getMediaPermissions: async () => {
|
||||||
|
return (await window.IPC.getMediaPermissions()) ?? false;
|
||||||
|
},
|
||||||
|
setMediaPermissions: async () => {
|
||||||
|
const forCalling = true;
|
||||||
|
await window.IPC.showPermissionsPopup(forCalling, false);
|
||||||
},
|
},
|
||||||
|
|
||||||
getZoomFactor: () => {
|
getZoomFactor: () => {
|
||||||
return ipcRenderer.invoke('getZoomFactor');
|
return ipcRenderer.invoke('getZoomFactor');
|
||||||
},
|
},
|
||||||
setZoomFactor: async zoomFactor => {
|
setZoomFactor: async zoomFactor => {
|
||||||
ipcRenderer.send('setZoomFactor', zoomFactor);
|
ipcRenderer.send('setZoomFactor', zoomFactor);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// From IPCEventsGettersType
|
||||||
onZoomFactorChange: callback => {
|
onZoomFactorChange: callback => {
|
||||||
ipcRenderer.on('zoomFactorChanged', (_event, zoomFactor) => {
|
zoomFactorChangeCallbacks.push(callback);
|
||||||
callback(zoomFactor);
|
},
|
||||||
});
|
offZoomFactorChange: toRemove => {
|
||||||
|
zoomFactorChangeCallbacks = zoomFactorChangeCallbacks.filter(
|
||||||
|
callback => toRemove !== callback
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
setPhoneNumberDiscoverabilitySetting,
|
// From EphemeralSettings
|
||||||
setPhoneNumberSharingSetting: async (newValue: PhoneNumberSharingMode) => {
|
getLocaleOverride: async () => {
|
||||||
const account = window.ConversationController.getOurConversationOrThrow();
|
return (await getEphemeralSetting('localeOverride')) ?? null;
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
// Write profile after updating storage so that the write has up-to-date
|
|
||||||
// information.
|
|
||||||
await writeProfile(getConversation(account), {
|
|
||||||
keepAvatar: true,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
setLocaleOverride: async (value: string | null) => {
|
||||||
getHasStoriesDisabled: () =>
|
await setEphemeralSetting('localeOverride', value);
|
||||||
window.storage.get('hasStoriesDisabled', false),
|
|
||||||
setHasStoriesDisabled: async (value: boolean) => {
|
|
||||||
await window.storage.put('hasStoriesDisabled', value);
|
|
||||||
const account = window.ConversationController.getOurConversationOrThrow();
|
|
||||||
account.captureChange('hasStoriesDisabled');
|
|
||||||
window.textsecure.server?.onHasStoriesDisabledChange(value);
|
|
||||||
},
|
},
|
||||||
getContentProtection: () => {
|
getContentProtection: async () => {
|
||||||
return getEphemeralSetting('contentProtection');
|
return (
|
||||||
|
(await getEphemeralSetting('contentProtection')) ??
|
||||||
|
Settings.isContentProtectionEnabledByDefault(
|
||||||
|
OS,
|
||||||
|
window.SignalContext.config.osRelease
|
||||||
|
)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
setContentProtection: async (value: boolean) => {
|
setContentProtection: async (value: boolean) => {
|
||||||
await setEphemeralSetting('contentProtection', value);
|
await setEphemeralSetting('contentProtection', value);
|
||||||
},
|
},
|
||||||
getStoryViewReceiptsEnabled: () => {
|
getSpellCheck: async () => {
|
||||||
|
return (await getEphemeralSetting('spellCheck')) ?? false;
|
||||||
|
},
|
||||||
|
setSpellCheck: async (value: boolean) => {
|
||||||
|
await setEphemeralSetting('spellCheck', value);
|
||||||
|
},
|
||||||
|
getSystemTraySetting: async () => {
|
||||||
return (
|
return (
|
||||||
window.storage.get('storyViewReceiptsEnabled') ??
|
(await getEphemeralSetting('systemTraySetting')) ??
|
||||||
window.storage.get('read-receipt-setting') ??
|
SystemTraySetting.Uninitialized
|
||||||
false
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
setStoryViewReceiptsEnabled: async (value: boolean) => {
|
setSystemTraySetting: async (value: SystemTraySetting) => {
|
||||||
await window.storage.put('storyViewReceiptsEnabled', value);
|
await setEphemeralSetting('systemTraySetting', value);
|
||||||
const account = window.ConversationController.getOurConversationOrThrow();
|
|
||||||
account.captureChange('storyViewReceiptsEnabled');
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getPreferredAudioInputDevice: () =>
|
|
||||||
window.storage.get('preferred-audio-input-device'),
|
|
||||||
setPreferredAudioInputDevice: device =>
|
|
||||||
window.storage.put('preferred-audio-input-device', device),
|
|
||||||
getPreferredAudioOutputDevice: () =>
|
|
||||||
window.storage.get('preferred-audio-output-device'),
|
|
||||||
setPreferredAudioOutputDevice: device =>
|
|
||||||
window.storage.put('preferred-audio-output-device', device),
|
|
||||||
getPreferredVideoInputDevice: () =>
|
|
||||||
window.storage.get('preferred-video-input-device'),
|
|
||||||
setPreferredVideoInputDevice: device =>
|
|
||||||
window.storage.put('preferred-video-input-device', device),
|
|
||||||
|
|
||||||
deleteAllMyStories: async () => {
|
|
||||||
await deleteAllMyStories();
|
|
||||||
},
|
|
||||||
|
|
||||||
setGlobalDefaultConversationColor: (...args) =>
|
|
||||||
window.reduxActions.items.setGlobalDefaultConversationColor(...args),
|
|
||||||
setEmojiSkinToneDefault: (emojiSkinTone: EmojiSkinTone) =>
|
|
||||||
window.reduxActions.items.setEmojiSkinToneDefault(emojiSkinTone),
|
|
||||||
|
|
||||||
// Chat Color redux hookups
|
|
||||||
getCustomColors: () => {
|
|
||||||
return getCustomColors(window.reduxStore.getState()) || {};
|
|
||||||
},
|
|
||||||
getConversationsWithCustomColor: colorId => {
|
|
||||||
return getConversationsWithCustomColorSelector(
|
|
||||||
window.reduxStore.getState()
|
|
||||||
)(colorId);
|
|
||||||
},
|
|
||||||
addCustomColor: (...args) =>
|
|
||||||
window.reduxActions.items.addCustomColor(...args),
|
|
||||||
editCustomColor: (...args) =>
|
|
||||||
window.reduxActions.items.editCustomColor(...args),
|
|
||||||
removeCustomColor: colorId =>
|
|
||||||
window.reduxActions.items.removeCustomColor(colorId),
|
|
||||||
removeCustomColorOnConversations: colorId =>
|
|
||||||
window.reduxActions.conversations.removeCustomColorOnConversations(
|
|
||||||
colorId
|
|
||||||
),
|
|
||||||
resetAllChatColors: () =>
|
|
||||||
window.reduxActions.conversations.resetAllChatColors(),
|
|
||||||
resetDefaultChatColor: () =>
|
|
||||||
window.reduxActions.items.resetDefaultChatColor(),
|
|
||||||
|
|
||||||
// Getters only
|
|
||||||
getAvailableIODevices: async () => {
|
|
||||||
const { availableCameras, availableMicrophones, availableSpeakers } =
|
|
||||||
await calling.getAvailableIODevices();
|
|
||||||
|
|
||||||
return {
|
|
||||||
// mapping it to a pojo so that it is IPC friendly
|
|
||||||
availableCameras: availableCameras.map(
|
|
||||||
(inputDeviceInfo: MediaDeviceInfo) => ({
|
|
||||||
deviceId: inputDeviceInfo.deviceId,
|
|
||||||
groupId: inputDeviceInfo.groupId,
|
|
||||||
kind: inputDeviceInfo.kind,
|
|
||||||
label: inputDeviceInfo.label,
|
|
||||||
})
|
|
||||||
),
|
|
||||||
availableMicrophones,
|
|
||||||
availableSpeakers,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
getBackupFeatureEnabled: () => {
|
|
||||||
return isBackupFeatureEnabled();
|
|
||||||
},
|
|
||||||
getCloudBackupStatus: () => {
|
|
||||||
return window.storage.get('cloudBackupStatus');
|
|
||||||
},
|
|
||||||
getBackupSubscriptionStatus: () => {
|
|
||||||
return window.storage.get('backupSubscriptionStatus');
|
|
||||||
},
|
|
||||||
refreshCloudBackupStatus:
|
|
||||||
window.Signal.Services.backups.throttledFetchCloudBackupStatus,
|
|
||||||
refreshBackupSubscriptionStatus:
|
|
||||||
window.Signal.Services.backups.throttledFetchSubscriptionStatus,
|
|
||||||
getBlockedCount: () =>
|
|
||||||
window.storage.blocked.getBlockedServiceIds().length +
|
|
||||||
window.storage.blocked.getBlockedGroups().length,
|
|
||||||
getDefaultConversationColor: () =>
|
|
||||||
window.storage.get(
|
|
||||||
'defaultConversationColor',
|
|
||||||
DEFAULT_CONVERSATION_COLOR
|
|
||||||
),
|
|
||||||
getEmojiSkinToneDefault: () =>
|
|
||||||
window.storage.get('emojiSkinToneDefault', EmojiSkinTone.None),
|
|
||||||
getLinkPreviewSetting: () => window.storage.get('linkPreviews', false),
|
|
||||||
getPhoneNumberDiscoverabilitySetting: () =>
|
|
||||||
window.storage.get(
|
|
||||||
'phoneNumberDiscoverability',
|
|
||||||
PhoneNumberDiscoverability.NotDiscoverable
|
|
||||||
),
|
|
||||||
getPhoneNumberSharingSetting: () =>
|
|
||||||
window.storage.get(
|
|
||||||
'phoneNumberSharingMode',
|
|
||||||
PhoneNumberSharingMode.Nobody
|
|
||||||
),
|
|
||||||
getReadReceiptSetting: () =>
|
|
||||||
window.storage.get('read-receipt-setting', false),
|
|
||||||
getTypingIndicatorSetting: () =>
|
|
||||||
window.storage.get('typingIndicators', false),
|
|
||||||
|
|
||||||
// Configurable settings
|
|
||||||
getAutoDownloadAttachment: () =>
|
|
||||||
window.storage.get(
|
|
||||||
'auto-download-attachment',
|
|
||||||
DEFAULT_AUTO_DOWNLOAD_ATTACHMENT
|
|
||||||
),
|
|
||||||
setAutoDownloadAttachment: (setting: AutoDownloadAttachmentType) =>
|
|
||||||
window.storage.put('auto-download-attachment', setting),
|
|
||||||
getAutoDownloadUpdate: () =>
|
|
||||||
window.storage.get('auto-download-update', true),
|
|
||||||
setAutoDownloadUpdate: value =>
|
|
||||||
window.storage.put('auto-download-update', value),
|
|
||||||
getAutoConvertEmoji: () => window.storage.get('autoConvertEmoji', true),
|
|
||||||
setAutoConvertEmoji: value => window.storage.put('autoConvertEmoji', value),
|
|
||||||
getSentMediaQualitySetting: () =>
|
|
||||||
window.storage.get('sent-media-quality', 'standard'),
|
|
||||||
setSentMediaQualitySetting: value =>
|
|
||||||
window.storage.put('sent-media-quality', value),
|
|
||||||
getThemeSetting: async () => {
|
getThemeSetting: async () => {
|
||||||
return getEphemeralSetting('themeSetting') ?? null;
|
return (await getEphemeralSetting('themeSetting')) ?? 'system';
|
||||||
},
|
},
|
||||||
setThemeSetting: async value => {
|
setThemeSetting: async (value: ThemeType) => {
|
||||||
drop(setEphemeralSetting('themeSetting', value));
|
await setEphemeralSetting('themeSetting', value);
|
||||||
},
|
|
||||||
updateThemeSetting: _theme => {
|
|
||||||
drop(themeChanged());
|
|
||||||
},
|
|
||||||
getHideMenuBar: () => window.storage.get('hide-menu-bar'),
|
|
||||||
setHideMenuBar: value => {
|
|
||||||
const promise = window.storage.put('hide-menu-bar', value);
|
|
||||||
window.IPC.setAutoHideMenuBar(value);
|
|
||||||
window.IPC.setMenuBarVisibility(!value);
|
|
||||||
return promise;
|
|
||||||
},
|
|
||||||
getSystemTraySetting: () => getEphemeralSetting('systemTraySetting'),
|
|
||||||
getLocaleOverride: async () => {
|
|
||||||
return getEphemeralSetting('localeOverride') ?? null;
|
|
||||||
},
|
|
||||||
getNotificationSetting: () =>
|
|
||||||
window.storage.get('notification-setting', 'message'),
|
|
||||||
setNotificationSetting: (value: 'message' | 'name' | 'count' | 'off') =>
|
|
||||||
window.storage.put('notification-setting', value),
|
|
||||||
getNotificationDrawAttention: () =>
|
|
||||||
window.storage.get('notification-draw-attention', false),
|
|
||||||
setNotificationDrawAttention: value =>
|
|
||||||
window.storage.put('notification-draw-attention', value),
|
|
||||||
getAudioMessage: () => window.storage.get('audioMessage', false),
|
|
||||||
setAudioMessage: value => window.storage.put('audioMessage', value),
|
|
||||||
getAudioNotification: () => window.storage.get('audio-notification'),
|
|
||||||
setAudioNotification: value =>
|
|
||||||
window.storage.put('audio-notification', value),
|
|
||||||
getCountMutedConversations: () =>
|
|
||||||
window.storage.get('badge-count-muted-conversations', false),
|
|
||||||
setCountMutedConversations: value => {
|
|
||||||
const promise = window.storage.put(
|
|
||||||
'badge-count-muted-conversations',
|
|
||||||
value
|
|
||||||
);
|
|
||||||
window.Whisper.events.trigger('updateUnreadCount');
|
|
||||||
return promise;
|
|
||||||
},
|
|
||||||
getCallRingtoneNotification: () =>
|
|
||||||
window.storage.get('call-ringtone-notification', true),
|
|
||||||
setCallRingtoneNotification: value =>
|
|
||||||
window.storage.put('call-ringtone-notification', value),
|
|
||||||
getCallSystemNotification: () =>
|
|
||||||
window.storage.get('call-system-notification', true),
|
|
||||||
setCallSystemNotification: value =>
|
|
||||||
window.storage.put('call-system-notification', value),
|
|
||||||
getIncomingCallNotification: () =>
|
|
||||||
window.storage.get('incoming-call-notification', true),
|
|
||||||
setIncomingCallNotification: value =>
|
|
||||||
window.storage.put('incoming-call-notification', value),
|
|
||||||
|
|
||||||
getSpellCheck: () => {
|
|
||||||
return getEphemeralSetting('spellCheck');
|
|
||||||
},
|
|
||||||
getTextFormatting: () => window.storage.get('textFormatting', true),
|
|
||||||
setTextFormatting: value => window.storage.put('textFormatting', value),
|
|
||||||
|
|
||||||
getAlwaysRelayCalls: () => window.storage.get('always-relay-calls'),
|
|
||||||
setAlwaysRelayCalls: value =>
|
|
||||||
window.storage.put('always-relay-calls', value),
|
|
||||||
|
|
||||||
getAutoLaunch: () => window.IPC.getAutoLaunch(),
|
|
||||||
setAutoLaunch: async (value: boolean) => {
|
|
||||||
return window.IPC.setAutoLaunch(value);
|
|
||||||
},
|
|
||||||
|
|
||||||
isPrimary: () => window.textsecure.storage.user.getDeviceId() === 1,
|
|
||||||
isInternalUser: () => isSettingsInternalEnabled(),
|
|
||||||
syncRequest: async () => {
|
|
||||||
const contactSyncComplete = waitForEvent(
|
|
||||||
'contactSync:complete',
|
|
||||||
5 * durations.MINUTE
|
|
||||||
);
|
|
||||||
await sendSyncRequests();
|
|
||||||
return contactSyncComplete;
|
|
||||||
},
|
|
||||||
// Only for internal use
|
|
||||||
exportLocalBackup: () => backupsService._internalExportLocalBackup(),
|
|
||||||
importLocalBackup: () =>
|
|
||||||
backupsService._internalStageLocalBackupForImport(),
|
|
||||||
validateBackup: () => backupsService._internalValidate(),
|
|
||||||
getLastSyncTime: () => window.storage.get('synced_at'),
|
|
||||||
setLastSyncTime: value => window.storage.put('synced_at', value),
|
|
||||||
getUniversalExpireTimer: () => universalExpireTimer.get(),
|
|
||||||
setUniversalExpireTimer: async newValue => {
|
|
||||||
await universalExpireTimer.set(newValue);
|
|
||||||
|
|
||||||
// Update account in Storage Service
|
|
||||||
const account = window.ConversationController.getOurConversationOrThrow();
|
|
||||||
account.captureChange('universalExpireTimer');
|
|
||||||
|
|
||||||
// Add a notification to the currently open conversation
|
|
||||||
const state = window.reduxStore.getState();
|
|
||||||
const selectedId = state.conversations.selectedConversationId;
|
|
||||||
if (selectedId) {
|
|
||||||
const conversation = window.ConversationController.get(selectedId);
|
|
||||||
assertDev(conversation, "Conversation wasn't found");
|
|
||||||
|
|
||||||
await conversation.updateLastMessage();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// From IPCEventsCallbacksType
|
||||||
addDarkOverlay: () => {
|
addDarkOverlay: () => {
|
||||||
const elems = document.querySelectorAll('.dark-overlay');
|
const elems = document.querySelectorAll('.dark-overlay');
|
||||||
if (elems.length) {
|
if (elems.length) {
|
||||||
|
@ -608,45 +246,57 @@ export function createIPCEvents(
|
||||||
elem.remove();
|
elem.remove();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
showKeyboardShortcuts: () =>
|
|
||||||
window.reduxActions.globalModals.showShortcutGuideModal(),
|
|
||||||
|
|
||||||
cleanupDownloads: async () => {
|
cleanupDownloads: async () => {
|
||||||
await ipcRenderer.invoke('cleanup-downloads');
|
await ipcRenderer.invoke('cleanup-downloads');
|
||||||
},
|
},
|
||||||
|
getIsInCall: (): boolean => {
|
||||||
deleteAllData: async () => {
|
return isInCall(window.reduxStore.getState());
|
||||||
renderClearingDataView();
|
|
||||||
},
|
},
|
||||||
|
getMediaAccessStatus: async (
|
||||||
showStickerPack: (packId, key) => {
|
mediaType: 'screen' | 'microphone' | 'camera'
|
||||||
// We can get these events even if the user has never linked this instance.
|
) => {
|
||||||
if (!Registration.everDone()) {
|
return window.IPC.getMediaAccessStatus(mediaType);
|
||||||
log.warn('showStickerPack: Not registered, returning early');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
window.reduxActions.globalModals.showStickerPackPreview(packId, key);
|
|
||||||
},
|
},
|
||||||
showGroupViaLink: async value => {
|
installStickerPack: async (packId, key) => {
|
||||||
// We can get these events even if the user has never linked this instance.
|
void Stickers.downloadStickerPack(packId, key, {
|
||||||
if (!Registration.everDone()) {
|
finalStatus: 'installed',
|
||||||
log.warn('showGroupViaLink: Not registered, returning early');
|
actionSource: 'ui',
|
||||||
return;
|
});
|
||||||
}
|
},
|
||||||
|
requestCloseConfirmation: async (): Promise<boolean> => {
|
||||||
try {
|
try {
|
||||||
await window.Signal.Groups.joinViaLink(value);
|
await new Promise<void>((resolve, reject) => {
|
||||||
} catch (error) {
|
showConfirmationDialog({
|
||||||
log.error(
|
dialogName: 'closeConfirmation',
|
||||||
'showGroupViaLink: Ran into an error!',
|
onTopOfEverything: true,
|
||||||
Errors.toLogFormat(error)
|
cancelText: window.i18n(
|
||||||
);
|
'icu:ConfirmationDialog__Title--close-requested-not-now'
|
||||||
window.reduxActions.globalModals.showErrorModal({
|
),
|
||||||
title: window.i18n('icu:GroupV2--join--general-join-failure--title'),
|
confirmStyle: 'negative',
|
||||||
description: window.i18n('icu:GroupV2--join--general-join-failure'),
|
title: window.i18n(
|
||||||
|
'icu:ConfirmationDialog__Title--in-call-close-requested'
|
||||||
|
),
|
||||||
|
okText: window.i18n('icu:close'),
|
||||||
|
reject: () => reject(),
|
||||||
|
resolve: () => resolve(),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
log.info('requestCloseConfirmation: Close confirmed by user.');
|
||||||
|
window.reduxActions.calling.hangUpActiveCall(
|
||||||
|
'User confirmed in-call close.'
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
log.info('requestCloseConfirmation: Close cancelled by user.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setMediaPlaybackDisabled: (playbackDisabled: boolean) => {
|
||||||
|
window.reduxActions?.lightbox.setPlaybackDisabled(playbackDisabled);
|
||||||
|
if (playbackDisabled) {
|
||||||
|
window.reduxActions?.audioPlayer.pauseVoiceNotePlayer();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
showConversationViaNotification({
|
showConversationViaNotification({
|
||||||
conversationId,
|
conversationId,
|
||||||
messageId,
|
messageId,
|
||||||
|
@ -667,7 +317,6 @@ export function createIPCEvents(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
showConversationViaToken(token: string) {
|
showConversationViaToken(token: string) {
|
||||||
const data = notificationService.resolveToken(token);
|
const data = notificationService.resolveToken(token);
|
||||||
if (!data) {
|
if (!data) {
|
||||||
|
@ -676,7 +325,6 @@ export function createIPCEvents(
|
||||||
window.Events.showConversationViaNotification(data);
|
window.Events.showConversationViaNotification(data);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async showConversationViaSignalDotMe(kind: string, value: string) {
|
async showConversationViaSignalDotMe(kind: string, value: string) {
|
||||||
if (!Registration.everDone()) {
|
if (!Registration.everDone()) {
|
||||||
log.info(
|
log.info(
|
||||||
|
@ -722,7 +370,40 @@ export function createIPCEvents(
|
||||||
log.info('showConversationViaSignalDotMe: invalid E164');
|
log.info('showConversationViaSignalDotMe: invalid E164');
|
||||||
showUnknownSgnlLinkModal();
|
showUnknownSgnlLinkModal();
|
||||||
},
|
},
|
||||||
|
showKeyboardShortcuts: () =>
|
||||||
|
window.reduxActions.globalModals.showShortcutGuideModal(),
|
||||||
|
showGroupViaLink: async value => {
|
||||||
|
// We can get these events even if the user has never linked this instance.
|
||||||
|
if (!Registration.everDone()) {
|
||||||
|
log.warn('showGroupViaLink: Not registered, returning early');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await window.Signal.Groups.joinViaLink(value);
|
||||||
|
} catch (error) {
|
||||||
|
log.error(
|
||||||
|
'showGroupViaLink: Ran into an error!',
|
||||||
|
Errors.toLogFormat(error)
|
||||||
|
);
|
||||||
|
window.reduxActions.globalModals.showErrorModal({
|
||||||
|
title: window.i18n('icu:GroupV2--join--general-join-failure--title'),
|
||||||
|
description: window.i18n('icu:GroupV2--join--general-join-failure'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
showReleaseNotes: () => {
|
||||||
|
const { showWhatsNewModal } = window.reduxActions.globalModals;
|
||||||
|
showWhatsNewModal();
|
||||||
|
},
|
||||||
|
showStickerPack: (packId, key) => {
|
||||||
|
// We can get these events even if the user has never linked this instance.
|
||||||
|
if (!Registration.everDone()) {
|
||||||
|
log.warn('showStickerPack: Not registered, returning early');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.reduxActions.globalModals.showStickerPackPreview(packId, key);
|
||||||
|
},
|
||||||
|
shutdown: () => Promise.resolve(),
|
||||||
startCallingLobbyViaToken(token: string) {
|
startCallingLobbyViaToken(token: string) {
|
||||||
const data = notificationService.resolveToken(token);
|
const data = notificationService.resolveToken(token);
|
||||||
if (!data) {
|
if (!data) {
|
||||||
|
@ -733,76 +414,10 @@ export function createIPCEvents(
|
||||||
isVideoCall: true,
|
isVideoCall: true,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
requestCloseConfirmation: async (): Promise<boolean> => {
|
|
||||||
try {
|
|
||||||
await new Promise<void>((resolve, reject) => {
|
|
||||||
showConfirmationDialog({
|
|
||||||
dialogName: 'closeConfirmation',
|
|
||||||
onTopOfEverything: true,
|
|
||||||
cancelText: window.i18n(
|
|
||||||
'icu:ConfirmationDialog__Title--close-requested-not-now'
|
|
||||||
),
|
|
||||||
confirmStyle: 'negative',
|
|
||||||
title: window.i18n(
|
|
||||||
'icu:ConfirmationDialog__Title--in-call-close-requested'
|
|
||||||
),
|
|
||||||
okText: window.i18n('icu:close'),
|
|
||||||
reject: () => reject(),
|
|
||||||
resolve: () => resolve(),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
log.info('requestCloseConfirmation: Close confirmed by user.');
|
|
||||||
window.reduxActions.calling.hangUpActiveCall(
|
|
||||||
'User confirmed in-call close.'
|
|
||||||
);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch {
|
|
||||||
log.info('requestCloseConfirmation: Close cancelled by user.');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getIsInCall: (): boolean => {
|
|
||||||
return isInCall(window.reduxStore.getState());
|
|
||||||
},
|
|
||||||
|
|
||||||
unknownSignalLink: () => {
|
unknownSignalLink: () => {
|
||||||
log.warn('unknownSignalLink: Showing error dialog');
|
log.warn('unknownSignalLink: Showing error dialog');
|
||||||
showUnknownSgnlLinkModal();
|
showUnknownSgnlLinkModal();
|
||||||
},
|
},
|
||||||
|
|
||||||
installStickerPack: async (packId, key) => {
|
|
||||||
void Stickers.downloadStickerPack(packId, key, {
|
|
||||||
finalStatus: 'installed',
|
|
||||||
actionSource: 'ui',
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
shutdown: () => Promise.resolve(),
|
|
||||||
showReleaseNotes: () => {
|
|
||||||
const { showWhatsNewModal } = window.reduxActions.globalModals;
|
|
||||||
showWhatsNewModal();
|
|
||||||
},
|
|
||||||
|
|
||||||
getMediaAccessStatus: async (
|
|
||||||
mediaType: 'screen' | 'microphone' | 'camera'
|
|
||||||
) => {
|
|
||||||
return window.IPC.getMediaAccessStatus(mediaType);
|
|
||||||
},
|
|
||||||
getMediaPermissions: window.IPC.getMediaPermissions,
|
|
||||||
getMediaCameraPermissions: async () => {
|
|
||||||
return (await window.IPC.getMediaCameraPermissions()) || false;
|
|
||||||
},
|
|
||||||
|
|
||||||
setMediaPlaybackDisabled: (playbackDisabled: boolean) => {
|
|
||||||
window.reduxActions?.lightbox.setPlaybackDisabled(playbackDisabled);
|
|
||||||
if (playbackDisabled) {
|
|
||||||
window.reduxActions?.audioPlayer.pauseVoiceNotePlayer();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
uploadStickerPack: (
|
uploadStickerPack: (
|
||||||
manifest: Uint8Array,
|
manifest: Uint8Array,
|
||||||
stickers: ReadonlyArray<Uint8Array>
|
stickers: ReadonlyArray<Uint8Array>
|
||||||
|
@ -812,7 +427,6 @@ export function createIPCEvents(
|
||||||
ipcRenderer.send('art-creator:onUploadProgress')
|
ipcRenderer.send('art-creator:onUploadProgress')
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
...overrideEvents,
|
...overrideEvents,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,3 +11,12 @@ export function isBackupFeatureEnabled(): boolean {
|
||||||
}
|
}
|
||||||
return Boolean(RemoteConfig.isEnabled('desktop.backup.credentialFetch'));
|
return Boolean(RemoteConfig.isEnabled('desktop.backup.credentialFetch'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isBackupFeatureEnabledForRedux(
|
||||||
|
config: RemoteConfig.ConfigMapType | undefined
|
||||||
|
): boolean {
|
||||||
|
if (isStagingServer() || isTestOrMockEnvironment()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return Boolean(config?.['desktop.backup.credentialFetch']?.enabled);
|
||||||
|
}
|
||||||
|
|
|
@ -82,6 +82,7 @@ async function fetchAndUpdateDeviceName() {
|
||||||
}
|
}
|
||||||
|
|
||||||
await window.storage.user.setDeviceName(newName);
|
await window.storage.user.setDeviceName(newName);
|
||||||
|
window.Whisper.events.trigger('deviceNameChanged');
|
||||||
log.info(
|
log.info(
|
||||||
'fetchAndUpdateDeviceName: successfully updated new device name locally'
|
'fetchAndUpdateDeviceName: successfully updated new device name locally'
|
||||||
);
|
);
|
||||||
|
|
|
@ -7,8 +7,8 @@ import { strictAssert } from './assert';
|
||||||
import * as Errors from '../types/errors';
|
import * as Errors from '../types/errors';
|
||||||
import type { UnwrapPromise } from '../types/Util';
|
import type { UnwrapPromise } from '../types/Util';
|
||||||
import type {
|
import type {
|
||||||
IPCEventsValuesType,
|
|
||||||
IPCEventsCallbacksType,
|
IPCEventsCallbacksType,
|
||||||
|
IPCEventsValuesType,
|
||||||
} from './createIPCEvents';
|
} from './createIPCEvents';
|
||||||
import type { SystemTraySetting } from '../types/SystemTraySetting';
|
import type { SystemTraySetting } from '../types/SystemTraySetting';
|
||||||
|
|
||||||
|
@ -25,11 +25,11 @@ export type SettingType<Value> = Readonly<{
|
||||||
export type ThemeType = 'light' | 'dark' | 'system';
|
export type ThemeType = 'light' | 'dark' | 'system';
|
||||||
|
|
||||||
export type EphemeralSettings = {
|
export type EphemeralSettings = {
|
||||||
|
localeOverride: string | null;
|
||||||
spellCheck: boolean;
|
spellCheck: boolean;
|
||||||
contentProtection: boolean;
|
contentProtection: boolean;
|
||||||
systemTraySetting: SystemTraySetting;
|
systemTraySetting: SystemTraySetting;
|
||||||
themeSetting: ThemeType;
|
themeSetting: ThemeType;
|
||||||
localeOverride: string | null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SettingsValuesType = IPCEventsValuesType & EphemeralSettings;
|
export type SettingsValuesType = IPCEventsValuesType & EphemeralSettings;
|
||||||
|
|
|
@ -9,7 +9,7 @@ export async function requestMicrophonePermissions(
|
||||||
await window.IPC.showPermissionsPopup(forCalling, false);
|
await window.IPC.showPermissionsPopup(forCalling, false);
|
||||||
|
|
||||||
// Check the setting again (from the source of truth).
|
// Check the setting again (from the source of truth).
|
||||||
return window.IPC.getMediaPermissions();
|
return window.Events.getMediaPermissions();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -2,6 +2,13 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
export const getStoriesDisabled = (): boolean =>
|
export const getStoriesDisabled = (): boolean =>
|
||||||
window.Events.getHasStoriesDisabled();
|
window.storage.get('hasStoriesDisabled', false);
|
||||||
|
|
||||||
|
export const setStoriesDisabled = async (value: boolean): Promise<void> => {
|
||||||
|
await window.storage.put('hasStoriesDisabled', value);
|
||||||
|
const account = window.ConversationController.getOurConversationOrThrow();
|
||||||
|
account.captureChange('hasStoriesDisabled');
|
||||||
|
window.textsecure.server?.onHasStoriesDisabledChange(value);
|
||||||
|
};
|
||||||
|
|
||||||
export const getStoriesBlocked = (): boolean => getStoriesDisabled();
|
export const getStoriesBlocked = (): boolean => getStoriesDisabled();
|
||||||
|
|
|
@ -2,12 +2,16 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { DurationInSeconds } from './durations';
|
import { DurationInSeconds } from './durations';
|
||||||
|
import type { ItemsStateType } from '../state/ducks/items';
|
||||||
|
|
||||||
export const ITEM_NAME = 'universalExpireTimer';
|
export const ITEM_NAME = 'universalExpireTimer';
|
||||||
|
|
||||||
export function get(): DurationInSeconds {
|
export function get(): DurationInSeconds {
|
||||||
return DurationInSeconds.fromSeconds(window.storage.get(ITEM_NAME) || 0);
|
return DurationInSeconds.fromSeconds(window.storage.get(ITEM_NAME) || 0);
|
||||||
}
|
}
|
||||||
|
export function getForRedux(items: ItemsStateType): DurationInSeconds {
|
||||||
|
return DurationInSeconds.fromSeconds(items[ITEM_NAME] || 0);
|
||||||
|
}
|
||||||
|
|
||||||
export function set(newValue: DurationInSeconds | undefined): Promise<void> {
|
export function set(newValue: DurationInSeconds | undefined): Promise<void> {
|
||||||
return window.storage.put(ITEM_NAME, newValue || DurationInSeconds.ZERO);
|
return window.storage.put(ITEM_NAME, newValue || DurationInSeconds.ZERO);
|
||||||
|
|
6
ts/window.d.ts
vendored
6
ts/window.d.ts
vendored
|
@ -66,7 +66,7 @@ export type IPCType = {
|
||||||
erase: () => Promise<void>;
|
erase: () => Promise<void>;
|
||||||
};
|
};
|
||||||
drawAttention: () => void;
|
drawAttention: () => void;
|
||||||
getAutoLaunch: () => Promise<boolean>;
|
getAutoLaunch: () => Promise<boolean | undefined>;
|
||||||
getMediaAccessStatus: (
|
getMediaAccessStatus: (
|
||||||
mediaType: 'screen' | 'microphone' | 'camera'
|
mediaType: 'screen' | 'microphone' | 'camera'
|
||||||
) => Promise<ReturnType<SystemPreferences['getMediaAccessStatus']>>;
|
) => Promise<ReturnType<SystemPreferences['getMediaAccessStatus']>>;
|
||||||
|
@ -74,7 +74,7 @@ export type IPCType = {
|
||||||
openSystemMediaPermissions: (
|
openSystemMediaPermissions: (
|
||||||
mediaType: 'microphone' | 'camera' | 'screenCapture'
|
mediaType: 'microphone' | 'camera' | 'screenCapture'
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
getMediaPermissions: () => Promise<boolean>;
|
getMediaPermissions: () => Promise<boolean | undefined>;
|
||||||
whenWindowVisible: () => Promise<void>;
|
whenWindowVisible: () => Promise<void>;
|
||||||
logAppLoadedEvent?: (options: { processedCount?: number }) => void;
|
logAppLoadedEvent?: (options: { processedCount?: number }) => void;
|
||||||
readyForUpdates: () => void;
|
readyForUpdates: () => void;
|
||||||
|
@ -82,6 +82,8 @@ export type IPCType = {
|
||||||
setAutoHideMenuBar: (value: boolean) => void;
|
setAutoHideMenuBar: (value: boolean) => void;
|
||||||
setAutoLaunch: (value: boolean) => Promise<void>;
|
setAutoLaunch: (value: boolean) => Promise<void>;
|
||||||
setBadge: (badge: number | 'marked-unread') => void;
|
setBadge: (badge: number | 'marked-unread') => void;
|
||||||
|
setMediaPermissions: (value: boolean) => Promise<void>;
|
||||||
|
setMediaCameraPermissions: (value: boolean) => Promise<void>;
|
||||||
setMenuBarVisibility: (value: boolean) => void;
|
setMenuBarVisibility: (value: boolean) => void;
|
||||||
showDebugLog: () => void;
|
showDebugLog: () => void;
|
||||||
showPermissionsPopup: (
|
showPermissionsPopup: (
|
||||||
|
|
|
@ -123,6 +123,10 @@ const IPC: IPCType = {
|
||||||
},
|
},
|
||||||
showPermissionsPopup: (forCalling, forCamera) =>
|
showPermissionsPopup: (forCalling, forCamera) =>
|
||||||
ipc.invoke('show-permissions-popup', forCalling, forCamera),
|
ipc.invoke('show-permissions-popup', forCalling, forCamera),
|
||||||
|
setMediaPermissions: (value: boolean) =>
|
||||||
|
ipc.invoke('settings:set:mediaPermissions', value),
|
||||||
|
setMediaCameraPermissions: (value: boolean) =>
|
||||||
|
ipc.invoke('settings:set:mediaCameraPermissions', value),
|
||||||
showSettings: () => ipc.send('show-settings'),
|
showSettings: () => ipc.send('show-settings'),
|
||||||
showWindow: () => {
|
showWindow: () => {
|
||||||
log.info('show window');
|
log.info('show window');
|
||||||
|
@ -263,6 +267,10 @@ ipc.on('additional-log-data-request', async event => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipc.on('open-settings-tab', () => {
|
||||||
|
window.Whisper.events.trigger('openSettingsTab');
|
||||||
|
});
|
||||||
|
|
||||||
ipc.on('set-up-as-new-device', () => {
|
ipc.on('set-up-as-new-device', () => {
|
||||||
window.Whisper.events.trigger('setupAsNewDevice');
|
window.Whisper.events.trigger('setupAsNewDevice');
|
||||||
});
|
});
|
||||||
|
@ -329,19 +337,6 @@ ipc.on('remove-dark-overlay', () => {
|
||||||
window.Events.removeDarkOverlay();
|
window.Events.removeDarkOverlay();
|
||||||
});
|
});
|
||||||
|
|
||||||
ipc.on('delete-all-data', async () => {
|
|
||||||
const { deleteAllData } = window.Events;
|
|
||||||
if (!deleteAllData) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await deleteAllData();
|
|
||||||
} catch (error) {
|
|
||||||
log.error('delete-all-data: error', Errors.toLogFormat(error));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ipc.on('show-sticker-pack', (_event, info) => {
|
ipc.on('show-sticker-pack', (_event, info) => {
|
||||||
window.Events.showStickerPack?.(info.packId, info.packKey);
|
window.Events.showStickerPack?.(info.packId, info.packKey);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,92 +1,10 @@
|
||||||
// Copyright 2017 Signal Messenger, LLC
|
// Copyright 2017 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import {
|
import { installEphemeralSetting } from '../util/preload';
|
||||||
installCallback,
|
|
||||||
installSetting,
|
|
||||||
installEphemeralSetting,
|
|
||||||
} from '../util/preload';
|
|
||||||
|
|
||||||
// ChatColorPicker redux hookups
|
installEphemeralSetting('contentProtection');
|
||||||
installCallback('getCustomColors');
|
|
||||||
installCallback('getConversationsWithCustomColor');
|
|
||||||
installCallback('addCustomColor');
|
|
||||||
installCallback('editCustomColor');
|
|
||||||
installCallback('removeCustomColor');
|
|
||||||
installCallback('removeCustomColorOnConversations');
|
|
||||||
installCallback('resetAllChatColors');
|
|
||||||
installCallback('resetDefaultChatColor');
|
|
||||||
installCallback('setGlobalDefaultConversationColor');
|
|
||||||
installCallback('getDefaultConversationColor');
|
|
||||||
|
|
||||||
installSetting('backupFeatureEnabled', {
|
|
||||||
setter: false,
|
|
||||||
});
|
|
||||||
installSetting('backupSubscriptionStatus', {
|
|
||||||
setter: false,
|
|
||||||
});
|
|
||||||
installSetting('cloudBackupStatus', {
|
|
||||||
setter: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Getters only. These are set by the primary device
|
|
||||||
installSetting('blockedCount', {
|
|
||||||
setter: false,
|
|
||||||
});
|
|
||||||
installSetting('linkPreviewSetting', {
|
|
||||||
setter: false,
|
|
||||||
});
|
|
||||||
installSetting('readReceiptSetting', {
|
|
||||||
setter: false,
|
|
||||||
});
|
|
||||||
installSetting('typingIndicatorSetting', {
|
|
||||||
setter: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
installCallback('refreshCloudBackupStatus');
|
|
||||||
installCallback('refreshBackupSubscriptionStatus');
|
|
||||||
installCallback('deleteAllMyStories');
|
|
||||||
installCallback('isPrimary');
|
|
||||||
installCallback('isInternalUser');
|
|
||||||
installCallback('syncRequest');
|
|
||||||
installCallback('getEmojiSkinToneDefault');
|
|
||||||
installCallback('setEmojiSkinToneDefault');
|
|
||||||
installCallback('exportLocalBackup');
|
|
||||||
installCallback('importLocalBackup');
|
|
||||||
installCallback('validateBackup');
|
|
||||||
|
|
||||||
installSetting('alwaysRelayCalls');
|
|
||||||
installSetting('audioMessage');
|
|
||||||
installSetting('audioNotification');
|
|
||||||
installSetting('autoConvertEmoji');
|
|
||||||
installSetting('autoDownloadUpdate');
|
|
||||||
installSetting('autoDownloadAttachment');
|
|
||||||
installSetting('autoLaunch');
|
|
||||||
installSetting('callRingtoneNotification');
|
|
||||||
installSetting('callSystemNotification');
|
|
||||||
installSetting('countMutedConversations');
|
|
||||||
installSetting('deviceName');
|
|
||||||
installSetting('phoneNumber');
|
|
||||||
installSetting('hasStoriesDisabled');
|
|
||||||
installSetting('hideMenuBar');
|
|
||||||
installSetting('incomingCallNotification');
|
|
||||||
installSetting('lastSyncTime');
|
|
||||||
installSetting('notificationDrawAttention');
|
|
||||||
installSetting('notificationSetting');
|
|
||||||
installSetting('sentMediaQualitySetting');
|
|
||||||
installSetting('textFormatting');
|
|
||||||
installSetting('universalExpireTimer');
|
|
||||||
installSetting('zoomFactor');
|
|
||||||
installSetting('phoneNumberDiscoverabilitySetting');
|
|
||||||
installSetting('phoneNumberSharingSetting');
|
|
||||||
|
|
||||||
// Media Settings
|
|
||||||
installCallback('getAvailableIODevices');
|
|
||||||
installSetting('preferredAudioInputDevice');
|
|
||||||
installSetting('preferredAudioOutputDevice');
|
|
||||||
installSetting('preferredVideoInputDevice');
|
|
||||||
|
|
||||||
installEphemeralSetting('themeSetting');
|
|
||||||
installEphemeralSetting('systemTraySetting');
|
|
||||||
installEphemeralSetting('localeOverride');
|
installEphemeralSetting('localeOverride');
|
||||||
installEphemeralSetting('spellCheck');
|
installEphemeralSetting('spellCheck');
|
||||||
|
installEphemeralSetting('systemTraySetting');
|
||||||
|
installEphemeralSetting('themeSetting');
|
||||||
|
|
|
@ -1,271 +0,0 @@
|
||||||
// Copyright 2023 Signal Messenger, LLC
|
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
import React, { StrictMode } from 'react';
|
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
|
|
||||||
import type { PropsPreloadType } from '../../components/Preferences';
|
|
||||||
import { i18n } from '../sandboxedInit';
|
|
||||||
import { Preferences } from '../../components/Preferences';
|
|
||||||
import { startInteractionMode } from '../../services/InteractionMode';
|
|
||||||
import { strictAssert } from '../../util/assert';
|
|
||||||
import { parseEnvironment, setEnvironment } from '../../environment';
|
|
||||||
|
|
||||||
const { SettingsWindowProps } = window.Signal;
|
|
||||||
|
|
||||||
strictAssert(SettingsWindowProps, 'window values not provided');
|
|
||||||
|
|
||||||
startInteractionMode();
|
|
||||||
|
|
||||||
setEnvironment(
|
|
||||||
parseEnvironment(window.SignalContext.getEnvironment()),
|
|
||||||
window.SignalContext.isTestOrMockEnvironment()
|
|
||||||
);
|
|
||||||
|
|
||||||
SettingsWindowProps.onRender(
|
|
||||||
({
|
|
||||||
addCustomColor,
|
|
||||||
autoDownloadAttachment,
|
|
||||||
availableCameras,
|
|
||||||
availableLocales,
|
|
||||||
availableMicrophones,
|
|
||||||
availableSpeakers,
|
|
||||||
backupFeatureEnabled,
|
|
||||||
backupSubscriptionStatus,
|
|
||||||
blockedCount,
|
|
||||||
closeSettings,
|
|
||||||
cloudBackupStatus,
|
|
||||||
customColors,
|
|
||||||
defaultConversationColor,
|
|
||||||
deviceName,
|
|
||||||
emojiSkinToneDefault,
|
|
||||||
phoneNumber,
|
|
||||||
doDeleteAllData,
|
|
||||||
doneRendering,
|
|
||||||
editCustomColor,
|
|
||||||
exportLocalBackup,
|
|
||||||
getConversationsWithCustomColor,
|
|
||||||
hasAudioNotifications,
|
|
||||||
hasAutoConvertEmoji,
|
|
||||||
hasAutoDownloadUpdate,
|
|
||||||
hasAutoLaunch,
|
|
||||||
hasCallNotifications,
|
|
||||||
hasCallRingtoneNotification,
|
|
||||||
hasContentProtection,
|
|
||||||
hasCountMutedConversations,
|
|
||||||
hasHideMenuBar,
|
|
||||||
hasIncomingCallNotifications,
|
|
||||||
hasLinkPreviews,
|
|
||||||
hasMediaCameraPermissions,
|
|
||||||
hasMediaPermissions,
|
|
||||||
hasMessageAudio,
|
|
||||||
hasMinimizeToAndStartInSystemTray,
|
|
||||||
hasMinimizeToSystemTray,
|
|
||||||
hasNotificationAttention,
|
|
||||||
hasNotifications,
|
|
||||||
hasReadReceipts,
|
|
||||||
hasRelayCalls,
|
|
||||||
hasSpellCheck,
|
|
||||||
hasStoriesDisabled,
|
|
||||||
hasTextFormatting,
|
|
||||||
hasTypingIndicators,
|
|
||||||
importLocalBackup,
|
|
||||||
initialSpellCheckSetting,
|
|
||||||
isAutoDownloadUpdatesSupported,
|
|
||||||
isAutoLaunchSupported,
|
|
||||||
isHideMenuBarSupported,
|
|
||||||
isMinimizeToAndStartInSystemTraySupported,
|
|
||||||
isNotificationAttentionSupported,
|
|
||||||
isSyncSupported,
|
|
||||||
isSystemTraySupported,
|
|
||||||
isContentProtectionSupported,
|
|
||||||
isContentProtectionNeeded,
|
|
||||||
isInternalUser,
|
|
||||||
lastSyncTime,
|
|
||||||
makeSyncRequest,
|
|
||||||
notificationContent,
|
|
||||||
onAudioNotificationsChange,
|
|
||||||
onAutoConvertEmojiChange,
|
|
||||||
onAutoDownloadAttachmentChange,
|
|
||||||
onAutoDownloadUpdateChange,
|
|
||||||
onAutoLaunchChange,
|
|
||||||
onCallNotificationsChange,
|
|
||||||
onCallRingtoneNotificationChange,
|
|
||||||
onContentProtectionChange,
|
|
||||||
onCountMutedConversationsChange,
|
|
||||||
onEmojiSkinToneDefaultChange,
|
|
||||||
onHasStoriesDisabledChanged,
|
|
||||||
onHideMenuBarChange,
|
|
||||||
onIncomingCallNotificationsChange,
|
|
||||||
onLastSyncTimeChange,
|
|
||||||
onLocaleChange,
|
|
||||||
onMediaCameraPermissionsChange,
|
|
||||||
onMediaPermissionsChange,
|
|
||||||
onMessageAudioChange,
|
|
||||||
onMinimizeToAndStartInSystemTrayChange,
|
|
||||||
onMinimizeToSystemTrayChange,
|
|
||||||
onNotificationAttentionChange,
|
|
||||||
onNotificationContentChange,
|
|
||||||
onNotificationsChange,
|
|
||||||
onRelayCallsChange,
|
|
||||||
onSelectedCameraChange,
|
|
||||||
onSelectedMicrophoneChange,
|
|
||||||
onSelectedSpeakerChange,
|
|
||||||
onSentMediaQualityChange,
|
|
||||||
onSpellCheckChange,
|
|
||||||
onTextFormattingChange,
|
|
||||||
onThemeChange,
|
|
||||||
onUniversalExpireTimerChange,
|
|
||||||
onWhoCanFindMeChange,
|
|
||||||
onWhoCanSeeMeChange,
|
|
||||||
onZoomFactorChange,
|
|
||||||
preferredSystemLocales,
|
|
||||||
refreshCloudBackupStatus,
|
|
||||||
refreshBackupSubscriptionStatus,
|
|
||||||
removeCustomColor,
|
|
||||||
removeCustomColorOnConversations,
|
|
||||||
resetAllChatColors,
|
|
||||||
resetDefaultChatColor,
|
|
||||||
resolvedLocale,
|
|
||||||
selectedCamera,
|
|
||||||
selectedMicrophone,
|
|
||||||
selectedSpeaker,
|
|
||||||
sentMediaQualitySetting,
|
|
||||||
setGlobalDefaultConversationColor,
|
|
||||||
localeOverride,
|
|
||||||
themeSetting,
|
|
||||||
universalExpireTimer,
|
|
||||||
validateBackup,
|
|
||||||
whoCanFindMe,
|
|
||||||
whoCanSeeMe,
|
|
||||||
zoomFactor,
|
|
||||||
}: PropsPreloadType) => {
|
|
||||||
ReactDOM.render(
|
|
||||||
<StrictMode>
|
|
||||||
<Preferences
|
|
||||||
addCustomColor={addCustomColor}
|
|
||||||
autoDownloadAttachment={autoDownloadAttachment}
|
|
||||||
availableCameras={availableCameras}
|
|
||||||
availableLocales={availableLocales}
|
|
||||||
availableMicrophones={availableMicrophones}
|
|
||||||
availableSpeakers={availableSpeakers}
|
|
||||||
backupFeatureEnabled={backupFeatureEnabled}
|
|
||||||
backupSubscriptionStatus={backupSubscriptionStatus}
|
|
||||||
blockedCount={blockedCount}
|
|
||||||
closeSettings={closeSettings}
|
|
||||||
cloudBackupStatus={cloudBackupStatus}
|
|
||||||
customColors={customColors}
|
|
||||||
defaultConversationColor={defaultConversationColor}
|
|
||||||
deviceName={deviceName}
|
|
||||||
emojiSkinToneDefault={emojiSkinToneDefault}
|
|
||||||
exportLocalBackup={exportLocalBackup}
|
|
||||||
phoneNumber={phoneNumber}
|
|
||||||
doDeleteAllData={doDeleteAllData}
|
|
||||||
doneRendering={doneRendering}
|
|
||||||
editCustomColor={editCustomColor}
|
|
||||||
getConversationsWithCustomColor={getConversationsWithCustomColor}
|
|
||||||
hasAudioNotifications={hasAudioNotifications}
|
|
||||||
hasAutoConvertEmoji={hasAutoConvertEmoji}
|
|
||||||
hasAutoDownloadUpdate={hasAutoDownloadUpdate}
|
|
||||||
hasAutoLaunch={hasAutoLaunch}
|
|
||||||
hasCallNotifications={hasCallNotifications}
|
|
||||||
hasCallRingtoneNotification={hasCallRingtoneNotification}
|
|
||||||
hasContentProtection={hasContentProtection}
|
|
||||||
hasCountMutedConversations={hasCountMutedConversations}
|
|
||||||
hasHideMenuBar={hasHideMenuBar}
|
|
||||||
hasIncomingCallNotifications={hasIncomingCallNotifications}
|
|
||||||
hasLinkPreviews={hasLinkPreviews}
|
|
||||||
hasMediaCameraPermissions={hasMediaCameraPermissions}
|
|
||||||
hasMediaPermissions={hasMediaPermissions}
|
|
||||||
hasMessageAudio={hasMessageAudio}
|
|
||||||
hasMinimizeToAndStartInSystemTray={hasMinimizeToAndStartInSystemTray}
|
|
||||||
hasMinimizeToSystemTray={hasMinimizeToSystemTray}
|
|
||||||
hasNotificationAttention={hasNotificationAttention}
|
|
||||||
hasNotifications={hasNotifications}
|
|
||||||
hasReadReceipts={hasReadReceipts}
|
|
||||||
hasRelayCalls={hasRelayCalls}
|
|
||||||
hasSpellCheck={hasSpellCheck}
|
|
||||||
hasStoriesDisabled={hasStoriesDisabled}
|
|
||||||
hasTextFormatting={hasTextFormatting}
|
|
||||||
hasTypingIndicators={hasTypingIndicators}
|
|
||||||
i18n={i18n}
|
|
||||||
importLocalBackup={importLocalBackup}
|
|
||||||
initialSpellCheckSetting={initialSpellCheckSetting}
|
|
||||||
isAutoDownloadUpdatesSupported={isAutoDownloadUpdatesSupported}
|
|
||||||
isAutoLaunchSupported={isAutoLaunchSupported}
|
|
||||||
isHideMenuBarSupported={isHideMenuBarSupported}
|
|
||||||
isMinimizeToAndStartInSystemTraySupported={
|
|
||||||
isMinimizeToAndStartInSystemTraySupported
|
|
||||||
}
|
|
||||||
isNotificationAttentionSupported={isNotificationAttentionSupported}
|
|
||||||
isSyncSupported={isSyncSupported}
|
|
||||||
isSystemTraySupported={isSystemTraySupported}
|
|
||||||
isContentProtectionSupported={isContentProtectionSupported}
|
|
||||||
isContentProtectionNeeded={isContentProtectionNeeded}
|
|
||||||
isInternalUser={isInternalUser}
|
|
||||||
lastSyncTime={lastSyncTime}
|
|
||||||
localeOverride={localeOverride}
|
|
||||||
makeSyncRequest={makeSyncRequest}
|
|
||||||
notificationContent={notificationContent}
|
|
||||||
onAudioNotificationsChange={onAudioNotificationsChange}
|
|
||||||
onAutoConvertEmojiChange={onAutoConvertEmojiChange}
|
|
||||||
onAutoDownloadAttachmentChange={onAutoDownloadAttachmentChange}
|
|
||||||
onAutoDownloadUpdateChange={onAutoDownloadUpdateChange}
|
|
||||||
onAutoLaunchChange={onAutoLaunchChange}
|
|
||||||
onCallNotificationsChange={onCallNotificationsChange}
|
|
||||||
onCallRingtoneNotificationChange={onCallRingtoneNotificationChange}
|
|
||||||
onContentProtectionChange={onContentProtectionChange}
|
|
||||||
onCountMutedConversationsChange={onCountMutedConversationsChange}
|
|
||||||
onEmojiSkinToneDefaultChange={onEmojiSkinToneDefaultChange}
|
|
||||||
onHasStoriesDisabledChanged={onHasStoriesDisabledChanged}
|
|
||||||
onHideMenuBarChange={onHideMenuBarChange}
|
|
||||||
onIncomingCallNotificationsChange={onIncomingCallNotificationsChange}
|
|
||||||
onLastSyncTimeChange={onLastSyncTimeChange}
|
|
||||||
onLocaleChange={onLocaleChange}
|
|
||||||
onMediaCameraPermissionsChange={onMediaCameraPermissionsChange}
|
|
||||||
onMediaPermissionsChange={onMediaPermissionsChange}
|
|
||||||
onMessageAudioChange={onMessageAudioChange}
|
|
||||||
onMinimizeToAndStartInSystemTrayChange={
|
|
||||||
onMinimizeToAndStartInSystemTrayChange
|
|
||||||
}
|
|
||||||
onMinimizeToSystemTrayChange={onMinimizeToSystemTrayChange}
|
|
||||||
onNotificationAttentionChange={onNotificationAttentionChange}
|
|
||||||
onNotificationContentChange={onNotificationContentChange}
|
|
||||||
onNotificationsChange={onNotificationsChange}
|
|
||||||
onRelayCallsChange={onRelayCallsChange}
|
|
||||||
onSelectedCameraChange={onSelectedCameraChange}
|
|
||||||
onSelectedMicrophoneChange={onSelectedMicrophoneChange}
|
|
||||||
onSelectedSpeakerChange={onSelectedSpeakerChange}
|
|
||||||
onSentMediaQualityChange={onSentMediaQualityChange}
|
|
||||||
onSpellCheckChange={onSpellCheckChange}
|
|
||||||
onTextFormattingChange={onTextFormattingChange}
|
|
||||||
onThemeChange={onThemeChange}
|
|
||||||
onUniversalExpireTimerChange={onUniversalExpireTimerChange}
|
|
||||||
onWhoCanFindMeChange={onWhoCanFindMeChange}
|
|
||||||
onWhoCanSeeMeChange={onWhoCanSeeMeChange}
|
|
||||||
onZoomFactorChange={onZoomFactorChange}
|
|
||||||
preferredSystemLocales={preferredSystemLocales}
|
|
||||||
refreshCloudBackupStatus={refreshCloudBackupStatus}
|
|
||||||
refreshBackupSubscriptionStatus={refreshBackupSubscriptionStatus}
|
|
||||||
removeCustomColorOnConversations={removeCustomColorOnConversations}
|
|
||||||
removeCustomColor={removeCustomColor}
|
|
||||||
resetAllChatColors={resetAllChatColors}
|
|
||||||
resetDefaultChatColor={resetDefaultChatColor}
|
|
||||||
resolvedLocale={resolvedLocale}
|
|
||||||
selectedCamera={selectedCamera}
|
|
||||||
selectedMicrophone={selectedMicrophone}
|
|
||||||
selectedSpeaker={selectedSpeaker}
|
|
||||||
sentMediaQualitySetting={sentMediaQualitySetting}
|
|
||||||
setGlobalDefaultConversationColor={setGlobalDefaultConversationColor}
|
|
||||||
themeSetting={themeSetting}
|
|
||||||
universalExpireTimer={universalExpireTimer}
|
|
||||||
validateBackup={validateBackup}
|
|
||||||
whoCanFindMe={whoCanFindMe}
|
|
||||||
whoCanSeeMe={whoCanSeeMe}
|
|
||||||
zoomFactor={zoomFactor}
|
|
||||||
/>
|
|
||||||
</StrictMode>,
|
|
||||||
document.getElementById('app')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
|
@ -1,537 +0,0 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
import { contextBridge, ipcRenderer } from 'electron';
|
|
||||||
import { MinimalSignalContext } from '../minimalContext';
|
|
||||||
|
|
||||||
import type { PropsPreloadType } from '../../components/Preferences';
|
|
||||||
import OS from '../../util/os/osPreload';
|
|
||||||
import * as Settings from '../../types/Settings';
|
|
||||||
import {
|
|
||||||
SystemTraySetting,
|
|
||||||
parseSystemTraySetting,
|
|
||||||
shouldMinimizeToSystemTray,
|
|
||||||
} from '../../types/SystemTraySetting';
|
|
||||||
import { awaitObject } from '../../util/awaitObject';
|
|
||||||
import { DurationInSeconds } from '../../util/durations';
|
|
||||||
import { createSetting, createCallback } from '../../util/preload';
|
|
||||||
import { findBestMatchingAudioDeviceIndex } from '../../calling/findBestMatchingDevice';
|
|
||||||
import type { EmojiSkinTone } from '../../components/fun/data/emojis';
|
|
||||||
|
|
||||||
function doneRendering() {
|
|
||||||
ipcRenderer.send('settings-done-rendering');
|
|
||||||
}
|
|
||||||
|
|
||||||
const settingMessageAudio = createSetting('audioMessage');
|
|
||||||
const settingAudioNotification = createSetting('audioNotification');
|
|
||||||
const settingAutoConvertEmoji = createSetting('autoConvertEmoji');
|
|
||||||
const settingAutoDownloadUpdate = createSetting('autoDownloadUpdate');
|
|
||||||
const settingAutoDownloadAttachment = createSetting('autoDownloadAttachment');
|
|
||||||
const settingAutoLaunch = createSetting('autoLaunch');
|
|
||||||
const settingCallRingtoneNotification = createSetting(
|
|
||||||
'callRingtoneNotification'
|
|
||||||
);
|
|
||||||
const settingCallSystemNotification = createSetting('callSystemNotification');
|
|
||||||
const settingCountMutedConversations = createSetting('countMutedConversations');
|
|
||||||
const settingDeviceName = createSetting('deviceName', { setter: false });
|
|
||||||
const settingPhoneNumber = createSetting('phoneNumber', { setter: false });
|
|
||||||
const settingHideMenuBar = createSetting('hideMenuBar');
|
|
||||||
const settingIncomingCallNotification = createSetting(
|
|
||||||
'incomingCallNotification'
|
|
||||||
);
|
|
||||||
const settingMediaCameraPermissions = createSetting('mediaCameraPermissions');
|
|
||||||
const settingMediaPermissions = createSetting('mediaPermissions');
|
|
||||||
const settingNotificationDrawAttention = createSetting(
|
|
||||||
'notificationDrawAttention'
|
|
||||||
);
|
|
||||||
const settingNotificationSetting = createSetting('notificationSetting');
|
|
||||||
const settingRelayCalls = createSetting('alwaysRelayCalls');
|
|
||||||
const settingSentMediaQuality = createSetting('sentMediaQualitySetting');
|
|
||||||
const settingSpellCheck = createSetting('spellCheck');
|
|
||||||
const settingContentProtection = createSetting('contentProtection');
|
|
||||||
const settingTextFormatting = createSetting('textFormatting');
|
|
||||||
const settingTheme = createSetting('themeSetting');
|
|
||||||
const settingSystemTraySetting = createSetting('systemTraySetting');
|
|
||||||
const settingLocaleOverride = createSetting('localeOverride');
|
|
||||||
|
|
||||||
const settingLastSyncTime = createSetting('lastSyncTime');
|
|
||||||
|
|
||||||
const settingHasStoriesDisabled = createSetting('hasStoriesDisabled');
|
|
||||||
const settingZoomFactor = createSetting('zoomFactor');
|
|
||||||
|
|
||||||
// Getters only.
|
|
||||||
const settingBlockedCount = createSetting('blockedCount');
|
|
||||||
const settingBackupFeatureEnabled = createSetting('backupFeatureEnabled', {
|
|
||||||
setter: false,
|
|
||||||
});
|
|
||||||
const settingCloudBackupStatus = createSetting('cloudBackupStatus', {
|
|
||||||
setter: false,
|
|
||||||
});
|
|
||||||
const settingBackupSubscriptionStatus = createSetting(
|
|
||||||
'backupSubscriptionStatus',
|
|
||||||
{
|
|
||||||
setter: false,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const settingLinkPreview = createSetting('linkPreviewSetting', {
|
|
||||||
setter: false,
|
|
||||||
});
|
|
||||||
const settingPhoneNumberDiscoverability = createSetting(
|
|
||||||
'phoneNumberDiscoverabilitySetting'
|
|
||||||
);
|
|
||||||
const settingPhoneNumberSharing = createSetting('phoneNumberSharingSetting');
|
|
||||||
const settingReadReceipts = createSetting('readReceiptSetting', {
|
|
||||||
setter: false,
|
|
||||||
});
|
|
||||||
const settingTypingIndicators = createSetting('typingIndicatorSetting', {
|
|
||||||
setter: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Media settings
|
|
||||||
const settingAudioInput = createSetting('preferredAudioInputDevice');
|
|
||||||
const settingAudioOutput = createSetting('preferredAudioOutputDevice');
|
|
||||||
const settingVideoInput = createSetting('preferredVideoInputDevice');
|
|
||||||
|
|
||||||
const settingUniversalExpireTimer = createSetting('universalExpireTimer');
|
|
||||||
|
|
||||||
// Callbacks
|
|
||||||
const ipcGetAvailableIODevices = createCallback('getAvailableIODevices');
|
|
||||||
const ipcGetCustomColors = createCallback('getCustomColors');
|
|
||||||
const ipcGetEmojiSkinToneDefault = createCallback('getEmojiSkinToneDefault');
|
|
||||||
const ipcIsSyncNotSupported = createCallback('isPrimary');
|
|
||||||
const ipcIsInternalUser = createCallback('isInternalUser');
|
|
||||||
const ipcMakeSyncRequest = createCallback('syncRequest');
|
|
||||||
const ipcExportLocalBackup = createCallback('exportLocalBackup');
|
|
||||||
const ipcImportLocalBackup = createCallback('importLocalBackup');
|
|
||||||
const ipcValidateBackup = createCallback('validateBackup');
|
|
||||||
const ipcDeleteAllMyStories = createCallback('deleteAllMyStories');
|
|
||||||
const ipcRefreshCloudBackupStatus = createCallback('refreshCloudBackupStatus');
|
|
||||||
const ipcRefreshBackupSubscriptionStatus = createCallback(
|
|
||||||
'refreshBackupSubscriptionStatus'
|
|
||||||
);
|
|
||||||
|
|
||||||
// ChatColorPicker redux hookups
|
|
||||||
// The redux actions update over IPC through a preferences re-render
|
|
||||||
const ipcGetDefaultConversationColor = createCallback(
|
|
||||||
'getDefaultConversationColor'
|
|
||||||
);
|
|
||||||
const ipcGetConversationsWithCustomColor = createCallback(
|
|
||||||
'getConversationsWithCustomColor'
|
|
||||||
);
|
|
||||||
const ipcAddCustomColor = createCallback('addCustomColor');
|
|
||||||
const ipcEditCustomColor = createCallback('editCustomColor');
|
|
||||||
const ipcRemoveCustomColor = createCallback('removeCustomColor');
|
|
||||||
const ipcRemoveCustomColorOnConversations = createCallback(
|
|
||||||
'removeCustomColorOnConversations'
|
|
||||||
);
|
|
||||||
const ipcResetAllChatColors = createCallback('resetAllChatColors');
|
|
||||||
const ipcResetDefaultChatColor = createCallback('resetDefaultChatColor');
|
|
||||||
const ipcSetGlobalDefaultConversationColor = createCallback(
|
|
||||||
'setGlobalDefaultConversationColor'
|
|
||||||
);
|
|
||||||
const ipcSetEmojiSkinToneDefault = createCallback('setEmojiSkinToneDefault');
|
|
||||||
|
|
||||||
const DEFAULT_NOTIFICATION_SETTING = 'message';
|
|
||||||
|
|
||||||
function getSystemTraySettingValues(systemTraySetting: SystemTraySetting): {
|
|
||||||
hasMinimizeToAndStartInSystemTray: boolean;
|
|
||||||
hasMinimizeToSystemTray: boolean;
|
|
||||||
} {
|
|
||||||
const parsedSystemTraySetting = parseSystemTraySetting(systemTraySetting);
|
|
||||||
const hasMinimizeToAndStartInSystemTray =
|
|
||||||
parsedSystemTraySetting ===
|
|
||||||
SystemTraySetting.MinimizeToAndStartInSystemTray;
|
|
||||||
const hasMinimizeToSystemTray = shouldMinimizeToSystemTray(
|
|
||||||
parsedSystemTraySetting
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
hasMinimizeToAndStartInSystemTray,
|
|
||||||
hasMinimizeToSystemTray,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let renderInBrowser = (_props: PropsPreloadType): void => {
|
|
||||||
throw new Error('render is not defined');
|
|
||||||
};
|
|
||||||
|
|
||||||
function attachRenderCallback<Value>(f: (value: Value) => Promise<Value>) {
|
|
||||||
return async (value: Value) => {
|
|
||||||
await f(value);
|
|
||||||
void renderPreferences();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function renderPreferences() {
|
|
||||||
const {
|
|
||||||
autoDownloadAttachment,
|
|
||||||
backupFeatureEnabled,
|
|
||||||
backupSubscriptionStatus,
|
|
||||||
blockedCount,
|
|
||||||
cloudBackupStatus,
|
|
||||||
deviceName,
|
|
||||||
emojiSkinToneDefault,
|
|
||||||
hasAudioNotifications,
|
|
||||||
hasAutoConvertEmoji,
|
|
||||||
hasAutoDownloadUpdate,
|
|
||||||
hasAutoLaunch,
|
|
||||||
hasCallNotifications,
|
|
||||||
hasCallRingtoneNotification,
|
|
||||||
hasContentProtection,
|
|
||||||
hasCountMutedConversations,
|
|
||||||
hasHideMenuBar,
|
|
||||||
hasIncomingCallNotifications,
|
|
||||||
hasLinkPreviews,
|
|
||||||
hasMediaCameraPermissions,
|
|
||||||
hasMediaPermissions,
|
|
||||||
hasMessageAudio,
|
|
||||||
hasNotificationAttention,
|
|
||||||
hasReadReceipts,
|
|
||||||
hasRelayCalls,
|
|
||||||
hasSpellCheck,
|
|
||||||
hasStoriesDisabled,
|
|
||||||
hasTextFormatting,
|
|
||||||
hasTypingIndicators,
|
|
||||||
lastSyncTime,
|
|
||||||
notificationContent,
|
|
||||||
phoneNumber,
|
|
||||||
selectedCamera,
|
|
||||||
selectedMicrophone,
|
|
||||||
selectedSpeaker,
|
|
||||||
sentMediaQualitySetting,
|
|
||||||
localeOverride,
|
|
||||||
systemTraySetting,
|
|
||||||
themeSetting,
|
|
||||||
universalExpireTimer,
|
|
||||||
whoCanFindMe,
|
|
||||||
whoCanSeeMe,
|
|
||||||
zoomFactor,
|
|
||||||
|
|
||||||
availableIODevices,
|
|
||||||
customColors,
|
|
||||||
defaultConversationColor,
|
|
||||||
isSyncNotSupported,
|
|
||||||
isInternalUser,
|
|
||||||
} = await awaitObject({
|
|
||||||
autoDownloadAttachment: settingAutoDownloadAttachment.getValue(),
|
|
||||||
backupFeatureEnabled: settingBackupFeatureEnabled.getValue(),
|
|
||||||
backupSubscriptionStatus: settingBackupSubscriptionStatus.getValue(),
|
|
||||||
blockedCount: settingBlockedCount.getValue(),
|
|
||||||
cloudBackupStatus: settingCloudBackupStatus.getValue(),
|
|
||||||
deviceName: settingDeviceName.getValue(),
|
|
||||||
hasAudioNotifications: settingAudioNotification.getValue(),
|
|
||||||
hasAutoConvertEmoji: settingAutoConvertEmoji.getValue(),
|
|
||||||
hasAutoDownloadUpdate: settingAutoDownloadUpdate.getValue(),
|
|
||||||
hasAutoLaunch: settingAutoLaunch.getValue(),
|
|
||||||
hasCallNotifications: settingCallSystemNotification.getValue(),
|
|
||||||
hasCallRingtoneNotification: settingCallRingtoneNotification.getValue(),
|
|
||||||
hasContentProtection: settingContentProtection.getValue(),
|
|
||||||
hasCountMutedConversations: settingCountMutedConversations.getValue(),
|
|
||||||
hasHideMenuBar: settingHideMenuBar.getValue(),
|
|
||||||
hasIncomingCallNotifications: settingIncomingCallNotification.getValue(),
|
|
||||||
hasLinkPreviews: settingLinkPreview.getValue(),
|
|
||||||
hasMediaCameraPermissions: settingMediaCameraPermissions.getValue(),
|
|
||||||
hasMediaPermissions: settingMediaPermissions.getValue(),
|
|
||||||
hasMessageAudio: settingMessageAudio.getValue(),
|
|
||||||
hasNotificationAttention: settingNotificationDrawAttention.getValue(),
|
|
||||||
hasReadReceipts: settingReadReceipts.getValue(),
|
|
||||||
hasRelayCalls: settingRelayCalls.getValue(),
|
|
||||||
hasSpellCheck: settingSpellCheck.getValue(),
|
|
||||||
hasStoriesDisabled: settingHasStoriesDisabled.getValue(),
|
|
||||||
hasTextFormatting: settingTextFormatting.getValue(),
|
|
||||||
hasTypingIndicators: settingTypingIndicators.getValue(),
|
|
||||||
lastSyncTime: settingLastSyncTime.getValue(),
|
|
||||||
notificationContent: settingNotificationSetting.getValue(),
|
|
||||||
phoneNumber: settingPhoneNumber.getValue(),
|
|
||||||
selectedCamera: settingVideoInput.getValue(),
|
|
||||||
selectedMicrophone: settingAudioInput.getValue(),
|
|
||||||
selectedSpeaker: settingAudioOutput.getValue(),
|
|
||||||
sentMediaQualitySetting: settingSentMediaQuality.getValue(),
|
|
||||||
localeOverride: settingLocaleOverride.getValue(),
|
|
||||||
systemTraySetting: settingSystemTraySetting.getValue(),
|
|
||||||
themeSetting: settingTheme.getValue(),
|
|
||||||
universalExpireTimer: settingUniversalExpireTimer.getValue(),
|
|
||||||
whoCanFindMe: settingPhoneNumberDiscoverability.getValue(),
|
|
||||||
whoCanSeeMe: settingPhoneNumberSharing.getValue(),
|
|
||||||
zoomFactor: settingZoomFactor.getValue(),
|
|
||||||
|
|
||||||
// Callbacks
|
|
||||||
availableIODevices: ipcGetAvailableIODevices(),
|
|
||||||
customColors: ipcGetCustomColors(),
|
|
||||||
defaultConversationColor: ipcGetDefaultConversationColor(),
|
|
||||||
emojiSkinToneDefault: ipcGetEmojiSkinToneDefault(),
|
|
||||||
isSyncNotSupported: ipcIsSyncNotSupported(),
|
|
||||||
isInternalUser: ipcIsInternalUser(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const { availableCameras, availableMicrophones, availableSpeakers } =
|
|
||||||
availableIODevices;
|
|
||||||
|
|
||||||
const { hasMinimizeToAndStartInSystemTray, hasMinimizeToSystemTray } =
|
|
||||||
getSystemTraySettingValues(systemTraySetting);
|
|
||||||
|
|
||||||
const onUniversalExpireTimerChange = attachRenderCallback(
|
|
||||||
settingUniversalExpireTimer.setValue
|
|
||||||
);
|
|
||||||
|
|
||||||
const availableLocales = MinimalSignalContext.getI18nAvailableLocales();
|
|
||||||
const resolvedLocale = MinimalSignalContext.getI18nLocale();
|
|
||||||
const preferredSystemLocales =
|
|
||||||
MinimalSignalContext.getPreferredSystemLocales();
|
|
||||||
|
|
||||||
const selectedMicIndex = findBestMatchingAudioDeviceIndex(
|
|
||||||
{
|
|
||||||
available: availableMicrophones,
|
|
||||||
preferred: selectedMicrophone,
|
|
||||||
},
|
|
||||||
OS.isWindows()
|
|
||||||
);
|
|
||||||
const recomputedSelectedMicrophone =
|
|
||||||
selectedMicIndex !== undefined
|
|
||||||
? availableMicrophones[selectedMicIndex]
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const selectedSpeakerIndex = findBestMatchingAudioDeviceIndex(
|
|
||||||
{
|
|
||||||
available: availableSpeakers,
|
|
||||||
preferred: selectedSpeaker,
|
|
||||||
},
|
|
||||||
OS.isWindows()
|
|
||||||
);
|
|
||||||
const recomputedSelectedSpeaker =
|
|
||||||
selectedSpeakerIndex !== undefined
|
|
||||||
? availableSpeakers[selectedSpeakerIndex]
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const props: PropsPreloadType = {
|
|
||||||
// Settings
|
|
||||||
autoDownloadAttachment,
|
|
||||||
availableCameras,
|
|
||||||
availableLocales,
|
|
||||||
availableMicrophones,
|
|
||||||
availableSpeakers,
|
|
||||||
backupFeatureEnabled,
|
|
||||||
backupSubscriptionStatus,
|
|
||||||
blockedCount,
|
|
||||||
cloudBackupStatus,
|
|
||||||
customColors,
|
|
||||||
defaultConversationColor,
|
|
||||||
deviceName,
|
|
||||||
emojiSkinToneDefault,
|
|
||||||
hasAudioNotifications,
|
|
||||||
hasAutoConvertEmoji,
|
|
||||||
hasAutoDownloadUpdate,
|
|
||||||
hasAutoLaunch,
|
|
||||||
hasCallNotifications,
|
|
||||||
hasCallRingtoneNotification,
|
|
||||||
hasContentProtection:
|
|
||||||
hasContentProtection ??
|
|
||||||
Settings.isContentProtectionEnabledByDefault(
|
|
||||||
OS,
|
|
||||||
MinimalSignalContext.config.osRelease
|
|
||||||
),
|
|
||||||
hasCountMutedConversations,
|
|
||||||
hasHideMenuBar,
|
|
||||||
hasIncomingCallNotifications,
|
|
||||||
hasLinkPreviews,
|
|
||||||
hasMediaCameraPermissions,
|
|
||||||
hasMediaPermissions,
|
|
||||||
hasMessageAudio,
|
|
||||||
hasMinimizeToAndStartInSystemTray,
|
|
||||||
hasMinimizeToSystemTray,
|
|
||||||
hasNotificationAttention,
|
|
||||||
hasNotifications: notificationContent !== 'off',
|
|
||||||
hasReadReceipts,
|
|
||||||
hasRelayCalls,
|
|
||||||
hasSpellCheck,
|
|
||||||
hasStoriesDisabled,
|
|
||||||
hasTextFormatting,
|
|
||||||
hasTypingIndicators,
|
|
||||||
lastSyncTime,
|
|
||||||
localeOverride,
|
|
||||||
notificationContent,
|
|
||||||
phoneNumber,
|
|
||||||
preferredSystemLocales,
|
|
||||||
resolvedLocale,
|
|
||||||
selectedCamera,
|
|
||||||
selectedMicrophone: recomputedSelectedMicrophone,
|
|
||||||
selectedSpeaker: recomputedSelectedSpeaker,
|
|
||||||
sentMediaQualitySetting,
|
|
||||||
themeSetting,
|
|
||||||
universalExpireTimer: DurationInSeconds.fromSeconds(universalExpireTimer),
|
|
||||||
whoCanFindMe,
|
|
||||||
whoCanSeeMe,
|
|
||||||
zoomFactor,
|
|
||||||
|
|
||||||
// Actions and other props
|
|
||||||
addCustomColor: ipcAddCustomColor,
|
|
||||||
closeSettings: () => MinimalSignalContext.executeMenuRole('close'),
|
|
||||||
doDeleteAllData: () => ipcRenderer.send('delete-all-data'),
|
|
||||||
doneRendering,
|
|
||||||
editCustomColor: ipcEditCustomColor,
|
|
||||||
getConversationsWithCustomColor: ipcGetConversationsWithCustomColor,
|
|
||||||
initialSpellCheckSetting:
|
|
||||||
MinimalSignalContext.config.appStartInitialSpellcheckSetting,
|
|
||||||
makeSyncRequest: ipcMakeSyncRequest,
|
|
||||||
refreshCloudBackupStatus: ipcRefreshCloudBackupStatus,
|
|
||||||
refreshBackupSubscriptionStatus: ipcRefreshBackupSubscriptionStatus,
|
|
||||||
removeCustomColor: ipcRemoveCustomColor,
|
|
||||||
removeCustomColorOnConversations: ipcRemoveCustomColorOnConversations,
|
|
||||||
resetAllChatColors: ipcResetAllChatColors,
|
|
||||||
resetDefaultChatColor: ipcResetDefaultChatColor,
|
|
||||||
setGlobalDefaultConversationColor: ipcSetGlobalDefaultConversationColor,
|
|
||||||
exportLocalBackup: ipcExportLocalBackup,
|
|
||||||
importLocalBackup: ipcImportLocalBackup,
|
|
||||||
validateBackup: ipcValidateBackup,
|
|
||||||
// Limited support features
|
|
||||||
isAutoDownloadUpdatesSupported: Settings.isAutoDownloadUpdatesSupported(
|
|
||||||
OS,
|
|
||||||
MinimalSignalContext.getVersion()
|
|
||||||
),
|
|
||||||
isAutoLaunchSupported: Settings.isAutoLaunchSupported(OS),
|
|
||||||
isHideMenuBarSupported: Settings.isHideMenuBarSupported(OS),
|
|
||||||
isNotificationAttentionSupported: Settings.isDrawAttentionSupported(OS),
|
|
||||||
isSyncSupported: !isSyncNotSupported,
|
|
||||||
isInternalUser,
|
|
||||||
isSystemTraySupported: Settings.isSystemTraySupported(OS),
|
|
||||||
isContentProtectionSupported: Settings.isContentProtectionSupported(OS),
|
|
||||||
isContentProtectionNeeded: Settings.isContentProtectionNeeded(OS),
|
|
||||||
isMinimizeToAndStartInSystemTraySupported:
|
|
||||||
Settings.isMinimizeToAndStartInSystemTraySupported(OS),
|
|
||||||
|
|
||||||
// Change handlers
|
|
||||||
onAudioNotificationsChange: attachRenderCallback(
|
|
||||||
settingAudioNotification.setValue
|
|
||||||
),
|
|
||||||
onAutoConvertEmojiChange: attachRenderCallback(
|
|
||||||
settingAutoConvertEmoji.setValue
|
|
||||||
),
|
|
||||||
onAutoDownloadUpdateChange: attachRenderCallback(
|
|
||||||
settingAutoDownloadUpdate.setValue
|
|
||||||
),
|
|
||||||
onAutoDownloadAttachmentChange: attachRenderCallback(
|
|
||||||
settingAutoDownloadAttachment.setValue
|
|
||||||
),
|
|
||||||
onAutoLaunchChange: attachRenderCallback(settingAutoLaunch.setValue),
|
|
||||||
onCallNotificationsChange: attachRenderCallback(
|
|
||||||
settingCallSystemNotification.setValue
|
|
||||||
),
|
|
||||||
onCallRingtoneNotificationChange: attachRenderCallback(
|
|
||||||
settingCallRingtoneNotification.setValue
|
|
||||||
),
|
|
||||||
onContentProtectionChange: attachRenderCallback(
|
|
||||||
settingContentProtection.setValue
|
|
||||||
),
|
|
||||||
onCountMutedConversationsChange: attachRenderCallback(
|
|
||||||
settingCountMutedConversations.setValue
|
|
||||||
),
|
|
||||||
onEmojiSkinToneDefaultChange: attachRenderCallback(
|
|
||||||
async (emojiSkinTone: EmojiSkinTone) => {
|
|
||||||
await ipcSetEmojiSkinToneDefault(emojiSkinTone);
|
|
||||||
return emojiSkinTone;
|
|
||||||
}
|
|
||||||
),
|
|
||||||
onHasStoriesDisabledChanged: attachRenderCallback(
|
|
||||||
async (value: boolean) => {
|
|
||||||
await settingHasStoriesDisabled.setValue(value);
|
|
||||||
if (!value) {
|
|
||||||
void ipcDeleteAllMyStories();
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
),
|
|
||||||
onHideMenuBarChange: attachRenderCallback(settingHideMenuBar.setValue),
|
|
||||||
onIncomingCallNotificationsChange: attachRenderCallback(
|
|
||||||
settingIncomingCallNotification.setValue
|
|
||||||
),
|
|
||||||
onLastSyncTimeChange: attachRenderCallback(settingLastSyncTime.setValue),
|
|
||||||
onLocaleChange: async (locale: string | null) => {
|
|
||||||
await settingLocaleOverride.setValue(locale);
|
|
||||||
MinimalSignalContext.restartApp();
|
|
||||||
},
|
|
||||||
onMediaCameraPermissionsChange: attachRenderCallback(
|
|
||||||
settingMediaCameraPermissions.setValue
|
|
||||||
),
|
|
||||||
onMessageAudioChange: attachRenderCallback(settingMessageAudio.setValue),
|
|
||||||
onMinimizeToAndStartInSystemTrayChange: attachRenderCallback(
|
|
||||||
async (value: boolean) => {
|
|
||||||
await settingSystemTraySetting.setValue(
|
|
||||||
value
|
|
||||||
? SystemTraySetting.MinimizeToAndStartInSystemTray
|
|
||||||
: SystemTraySetting.MinimizeToSystemTray
|
|
||||||
);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
),
|
|
||||||
onMinimizeToSystemTrayChange: attachRenderCallback(
|
|
||||||
async (value: boolean) => {
|
|
||||||
await settingSystemTraySetting.setValue(
|
|
||||||
value
|
|
||||||
? SystemTraySetting.MinimizeToSystemTray
|
|
||||||
: SystemTraySetting.DoNotUseSystemTray
|
|
||||||
);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
),
|
|
||||||
onMediaPermissionsChange: attachRenderCallback(
|
|
||||||
settingMediaPermissions.setValue
|
|
||||||
),
|
|
||||||
onNotificationAttentionChange: attachRenderCallback(
|
|
||||||
settingNotificationDrawAttention.setValue
|
|
||||||
),
|
|
||||||
onNotificationContentChange: attachRenderCallback(
|
|
||||||
settingNotificationSetting.setValue
|
|
||||||
),
|
|
||||||
onNotificationsChange: attachRenderCallback(async (value: boolean) => {
|
|
||||||
await settingNotificationSetting.setValue(
|
|
||||||
value ? DEFAULT_NOTIFICATION_SETTING : 'off'
|
|
||||||
);
|
|
||||||
return value;
|
|
||||||
}),
|
|
||||||
onRelayCallsChange: attachRenderCallback(settingRelayCalls.setValue),
|
|
||||||
onSelectedCameraChange: attachRenderCallback(settingVideoInput.setValue),
|
|
||||||
onSelectedMicrophoneChange: attachRenderCallback(
|
|
||||||
settingAudioInput.setValue
|
|
||||||
),
|
|
||||||
onSelectedSpeakerChange: attachRenderCallback(settingAudioOutput.setValue),
|
|
||||||
onSentMediaQualityChange: attachRenderCallback(
|
|
||||||
settingSentMediaQuality.setValue
|
|
||||||
),
|
|
||||||
onSpellCheckChange: attachRenderCallback(settingSpellCheck.setValue),
|
|
||||||
onTextFormattingChange: attachRenderCallback(
|
|
||||||
settingTextFormatting.setValue
|
|
||||||
),
|
|
||||||
onThemeChange: attachRenderCallback(settingTheme.setValue),
|
|
||||||
onUniversalExpireTimerChange: (newValue: number): Promise<void> => {
|
|
||||||
return onUniversalExpireTimerChange(
|
|
||||||
DurationInSeconds.fromSeconds(newValue)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
onWhoCanFindMeChange: attachRenderCallback(
|
|
||||||
settingPhoneNumberDiscoverability.setValue
|
|
||||||
),
|
|
||||||
onWhoCanSeeMeChange: attachRenderCallback(
|
|
||||||
settingPhoneNumberSharing.setValue
|
|
||||||
),
|
|
||||||
onZoomFactorChange: (zoomFactorValue: number) => {
|
|
||||||
ipcRenderer.send('setZoomFactor', zoomFactorValue);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
renderInBrowser(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
ipcRenderer.on('preferences-changed', renderPreferences);
|
|
||||||
ipcRenderer.on('zoomFactorChanged', renderPreferences);
|
|
||||||
|
|
||||||
const Signal = {
|
|
||||||
SettingsWindowProps: {
|
|
||||||
onRender: (renderer: (_props: PropsPreloadType) => void) => {
|
|
||||||
renderInBrowser = renderer;
|
|
||||||
void renderPreferences();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
contextBridge.exposeInMainWorld('Signal', Signal);
|
|
||||||
contextBridge.exposeInMainWorld('SignalContext', MinimalSignalContext);
|
|
Loading…
Add table
Add a link
Reference in a new issue