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?",
|
||||
"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": {
|
||||
"messageformat": "Phone Number",
|
||||
"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'
|
||||
);
|
||||
await updater.start({
|
||||
settingsChannel,
|
||||
logger: getLogger(),
|
||||
getMainWindow,
|
||||
canRunSilently: () => {
|
||||
return (
|
||||
systemTrayService?.isVisible() === true &&
|
||||
|
@ -1183,6 +1180,9 @@ async function readyForUpdates() {
|
|||
mainWindow?.webContents?.getBackgroundThrottling() !== false
|
||||
);
|
||||
},
|
||||
getMainWindow,
|
||||
logger: getLogger(),
|
||||
sql,
|
||||
});
|
||||
} catch (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() {
|
||||
try {
|
||||
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.
|
||||
await createWindow();
|
||||
|
||||
|
@ -2322,8 +2273,6 @@ app.on('ready', async () => {
|
|||
return;
|
||||
}
|
||||
|
||||
appStartInitialSpellcheckSetting = await getSpellCheckSetting();
|
||||
|
||||
try {
|
||||
const IDB_KEY = 'indexeddb-delete-needed';
|
||||
const item = await sql.sqlRead('getItemById', IDB_KEY);
|
||||
|
@ -2382,7 +2331,15 @@ function setupMenu(options?: Partial<CreateTemplateOptionsType>) {
|
|||
showDebugLog: showDebugLogWindow,
|
||||
showCallingDevTools: showCallingDevToolsWindow,
|
||||
showKeyboardShortcuts,
|
||||
showSettings: showSettingsWindow,
|
||||
showSettings: () => {
|
||||
if (!settingsChannel) {
|
||||
getLogger().warn(
|
||||
'showSettings: No settings channel; cannot open settings tab.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
settingsChannel.openSettingsTab();
|
||||
},
|
||||
showWindow,
|
||||
zoomIn,
|
||||
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 => {
|
||||
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', 'permissions', 'app.tsx'),
|
||||
path.join(ROOT_DIR, 'ts', 'windows', 'screenShare', 'app.tsx'),
|
||||
path.join(ROOT_DIR, 'ts', 'windows', 'settings', 'app.tsx'),
|
||||
path.join(
|
||||
ROOT_DIR,
|
||||
'ts',
|
||||
|
@ -154,7 +153,6 @@ async function sandboxedEnv() {
|
|||
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', 'screenShare', 'preload.ts'),
|
||||
path.join(ROOT_DIR, 'ts', 'windows', 'settings', 'preload.ts'),
|
||||
],
|
||||
format: 'cjs',
|
||||
outdir: 'bundles',
|
||||
|
|
|
@ -170,6 +170,10 @@ $NavTabs__ProfileAvatar__size: 28px;
|
|||
|
||||
.NavTabs__ItemIcon--Settings {
|
||||
@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 {
|
||||
|
@ -201,13 +205,31 @@ $NavTabs__ProfileAvatar__size: 28px;
|
|||
}
|
||||
|
||||
.NavTabs__TabList {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
display: grid;
|
||||
grid-template-rows:
|
||||
[Chats] auto
|
||||
[Calls] auto
|
||||
[Stories] auto
|
||||
1fr
|
||||
[Settings] auto;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
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 {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
|
|
@ -24,6 +24,8 @@ $secondary-text-color: light-dark(
|
|||
display: flex;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
width: 100vw;
|
||||
|
||||
@include mixins.light-theme {
|
||||
background: variables.$color-white;
|
||||
}
|
||||
|
@ -31,14 +33,43 @@ $secondary-text-color: light-dark(
|
|||
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 {
|
||||
padding-top: calc(24px + var(--title-bar-drag-area-height));
|
||||
min-width: min(34%, 240px);
|
||||
padding-top: var(--title-bar-drag-area-height);
|
||||
width: 279px;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
@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 {
|
||||
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;
|
||||
display: flex;
|
||||
height: 48px;
|
||||
width: 100%;
|
||||
width: calc(100% - 20px);
|
||||
padding-block: 14px;
|
||||
padding-inline: 0;
|
||||
margin-inline-start: 10px;
|
||||
margin-inline-end: 10px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
&--selected {
|
||||
|
@ -130,12 +164,19 @@ $secondary-text-color: light-dark(
|
|||
height: 100vh;
|
||||
overflow: overlay;
|
||||
width: 100%;
|
||||
flex-grow: 1;
|
||||
max-width: 825px;
|
||||
|
||||
&::-webkit-scrollbar-corner {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
&__settings-pane-spacer {
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&__title {
|
||||
@include mixins.font-body-1-bold;
|
||||
align-items: center;
|
||||
|
|
|
@ -215,6 +215,7 @@ import { waitForEvent } from './shims/events';
|
|||
import { sendSyncRequests } from './textsecure/syncRequests';
|
||||
import { handleServerAlerts } from './util/handleServerAlerts';
|
||||
import { isLocalBackupsEnabled } from './util/isLocalBackupsEnabled';
|
||||
import { NavTab } from './state/ducks/nav';
|
||||
|
||||
export function isOverHourIntoPast(timestamp: number): boolean {
|
||||
return isNumber(timestamp) && isOlderThan(timestamp, HOUR);
|
||||
|
@ -1356,6 +1357,10 @@ export async function startApp(): Promise<void> {
|
|||
window.reduxActions.app.openStandalone();
|
||||
});
|
||||
|
||||
window.Whisper.events.on('openSettingsTab', () => {
|
||||
window.reduxActions.nav.changeNavTab(NavTab.Settings);
|
||||
});
|
||||
|
||||
window.Whisper.events.on('powerMonitorSuspend', () => {
|
||||
log.info('powerMonitor: suspend');
|
||||
server?.cancelInflightRequests('powerMonitorSuspend');
|
||||
|
|
|
@ -24,7 +24,7 @@ export default {
|
|||
addCustomColor: action('addCustomColor'),
|
||||
colorSelected: action('colorSelected'),
|
||||
editCustomColor: action('editCustomColor'),
|
||||
getConversationsWithCustomColor: (_: string) => Promise.resolve([]),
|
||||
getConversationsWithCustomColor: (_: string) => [],
|
||||
i18n,
|
||||
removeCustomColor: action('removeCustomColor'),
|
||||
removeCustomColorOnConversations: action(
|
||||
|
|
|
@ -26,9 +26,7 @@ type CustomColorDataType = {
|
|||
export type PropsDataType = {
|
||||
conversationId?: string;
|
||||
customColors?: Record<string, CustomColorType>;
|
||||
getConversationsWithCustomColor: (
|
||||
colorId: string
|
||||
) => Promise<Array<ConversationType>>;
|
||||
getConversationsWithCustomColor: (colorId: string) => Array<ConversationType>;
|
||||
i18n: LocalizerType;
|
||||
isGlobal?: boolean;
|
||||
selectedColor?: ConversationColorType;
|
||||
|
@ -270,9 +268,7 @@ export function ChatColorPicker({
|
|||
type CustomColorBubblePropsType = {
|
||||
color: CustomColorType;
|
||||
colorId: string;
|
||||
getConversationsWithCustomColor: (
|
||||
colorId: string
|
||||
) => Promise<Array<ConversationType>>;
|
||||
getConversationsWithCustomColor: (colorId: string) => Array<ConversationType>;
|
||||
i18n: LocalizerType;
|
||||
isSelected: boolean;
|
||||
onDelete: () => unknown;
|
||||
|
@ -393,12 +389,11 @@ function CustomColorBubble({
|
|||
attributes={{
|
||||
className: 'ChatColorPicker__context--delete',
|
||||
}}
|
||||
onClick={async (event: MouseEvent) => {
|
||||
onClick={(event: MouseEvent) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
const conversations =
|
||||
await getConversationsWithCustomColor(colorId);
|
||||
const conversations = getConversationsWithCustomColor(colorId);
|
||||
if (!conversations.length) {
|
||||
onDelete();
|
||||
} else {
|
||||
|
|
|
@ -3,16 +3,19 @@
|
|||
|
||||
import type { ReactNode } from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import { isBeta } from '../util/version';
|
||||
import { DialogType } from '../types/Dialogs';
|
||||
import type { LocalizerType } from '../types/Util';
|
||||
import { PRODUCTION_DOWNLOAD_URL, BETA_DOWNLOAD_URL } from '../types/support';
|
||||
import { I18n } from './I18n';
|
||||
import { LeftPaneDialog } from './LeftPaneDialog';
|
||||
import type { WidthBreakpoint } from './_util';
|
||||
import { formatFileSize } from '../util/formatFileSize';
|
||||
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 {
|
||||
const localizedSupportLink = getLocalizedUrl(
|
||||
'https://support.signal.org/hc/LOCALE/requests/new?desktop'
|
||||
|
@ -32,6 +35,7 @@ function contactSupportLink(parts: ReactNode): JSX.Element {
|
|||
export type PropsType = {
|
||||
containerWidthBreakpoint: WidthBreakpoint;
|
||||
dialogType: DialogType;
|
||||
disableDismiss?: boolean;
|
||||
dismissDialog: () => void;
|
||||
downloadSize?: number;
|
||||
downloadedSize?: number;
|
||||
|
@ -45,6 +49,7 @@ export type PropsType = {
|
|||
export function DialogUpdate({
|
||||
containerWidthBreakpoint,
|
||||
dialogType,
|
||||
disableDismiss,
|
||||
dismissDialog,
|
||||
downloadSize,
|
||||
downloadedSize,
|
||||
|
@ -215,6 +220,19 @@ export function DialogUpdate({
|
|||
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 (
|
||||
<LeftPaneDialog
|
||||
containerWidthBreakpoint={containerWidthBreakpoint}
|
||||
|
@ -225,9 +243,7 @@ export function DialogUpdate({
|
|||
hasAction
|
||||
onClick={startUpdate}
|
||||
clickLabel={clickLabel}
|
||||
hasXButton
|
||||
onClose={snoozeUpdate}
|
||||
closeLabel={i18n('icu:autoUpdateIgnoreButtonLabel')}
|
||||
{...dismissOptions}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ export type PropsType = {
|
|||
renderCustomizingPreferredReactionsModal: () => JSX.Element;
|
||||
renderNavTabs: (props: SmartNavTabsProps) => JSX.Element;
|
||||
renderStoriesTab: () => JSX.Element;
|
||||
renderSettingsTab: () => JSX.Element;
|
||||
};
|
||||
|
||||
const PART_COUNT = 16;
|
||||
|
@ -41,6 +42,7 @@ export function Inbox({
|
|||
renderCustomizingPreferredReactionsModal,
|
||||
renderNavTabs,
|
||||
renderStoriesTab,
|
||||
renderSettingsTab,
|
||||
}: PropsType): JSX.Element {
|
||||
const [internalHasInitialLoadCompleted, setInternalHasInitialLoadCompleted] =
|
||||
useState(hasInitialLoadCompleted);
|
||||
|
@ -200,6 +202,7 @@ export function Inbox({
|
|||
renderChatsTab,
|
||||
renderCallsTab,
|
||||
renderStoriesTab,
|
||||
renderSettingsTab,
|
||||
})}
|
||||
</div>
|
||||
{activeModal}
|
||||
|
|
|
@ -9,6 +9,17 @@ import { WidthBreakpoint } from './_util';
|
|||
|
||||
const BASE_CLASS_NAME = 'LeftPaneDialog';
|
||||
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 = {
|
||||
type?: 'warning' | 'error' | 'info';
|
||||
|
@ -30,18 +41,7 @@ export type PropsType = {
|
|||
hasAction: boolean;
|
||||
}
|
||||
) &
|
||||
(
|
||||
| {
|
||||
onClose?: undefined;
|
||||
closeLabel?: undefined;
|
||||
hasXButton?: false;
|
||||
}
|
||||
| {
|
||||
onClose: () => void;
|
||||
closeLabel: string;
|
||||
hasXButton: true;
|
||||
}
|
||||
);
|
||||
DismissOptions;
|
||||
|
||||
export function LeftPaneDialog({
|
||||
icon = 'warning',
|
||||
|
|
|
@ -13,7 +13,6 @@ import { NavTab } from '../state/ducks/nav';
|
|||
import { Tooltip, TooltipPlacement } from './Tooltip';
|
||||
import { Theme } from '../util/theme';
|
||||
import type { UnreadStats } from '../util/countUnreadStats';
|
||||
import { ContextMenu } from './ContextMenu';
|
||||
|
||||
type NavTabsItemBadgesProps = Readonly<{
|
||||
i18n: LocalizerType;
|
||||
|
@ -72,25 +71,33 @@ function NavTabsItemBadges({
|
|||
}
|
||||
|
||||
type NavTabProps = Readonly<{
|
||||
hasError?: boolean;
|
||||
i18n: LocalizerType;
|
||||
iconClassName: string;
|
||||
id: NavTab;
|
||||
hasError?: boolean;
|
||||
label: string;
|
||||
navTabClassName: string;
|
||||
unreadStats: UnreadStats | null;
|
||||
hasPendingUpdate?: boolean;
|
||||
}>;
|
||||
|
||||
function NavTabsItem({
|
||||
hasError,
|
||||
i18n,
|
||||
iconClassName,
|
||||
id,
|
||||
label,
|
||||
navTabClassName,
|
||||
unreadStats,
|
||||
hasError,
|
||||
hasPendingUpdate,
|
||||
}: NavTabProps) {
|
||||
const isRTL = i18n.getLocaleDirection() === 'rtl';
|
||||
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>
|
||||
<Tooltip
|
||||
content={label}
|
||||
|
@ -108,6 +115,7 @@ function NavTabsItem({
|
|||
i18n={i18n}
|
||||
unreadStats={unreadStats}
|
||||
hasError={hasError}
|
||||
hasPendingUpdate={hasPendingUpdate}
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
|
@ -187,14 +195,13 @@ export type NavTabsProps = Readonly<{
|
|||
i18n: LocalizerType;
|
||||
me: ConversationType;
|
||||
navTabsCollapsed: boolean;
|
||||
onShowSettings: () => void;
|
||||
onStartUpdate: () => unknown;
|
||||
onNavTabSelected: (tab: NavTab) => void;
|
||||
onToggleNavTabsCollapse: (collapsed: boolean) => void;
|
||||
onToggleProfileEditor: () => void;
|
||||
renderCallsTab: () => ReactNode;
|
||||
renderChatsTab: () => ReactNode;
|
||||
renderStoriesTab: () => ReactNode;
|
||||
renderSettingsTab: () => ReactNode;
|
||||
selectedNavTab: NavTab;
|
||||
storiesEnabled: boolean;
|
||||
theme: ThemeType;
|
||||
|
@ -210,14 +217,13 @@ export function NavTabs({
|
|||
i18n,
|
||||
me,
|
||||
navTabsCollapsed,
|
||||
onShowSettings,
|
||||
onStartUpdate,
|
||||
onNavTabSelected,
|
||||
onToggleNavTabsCollapse,
|
||||
onToggleProfileEditor,
|
||||
renderCallsTab,
|
||||
renderChatsTab,
|
||||
renderStoriesTab,
|
||||
renderSettingsTab,
|
||||
selectedNavTab,
|
||||
storiesEnabled,
|
||||
theme,
|
||||
|
@ -259,6 +265,7 @@ export function NavTabs({
|
|||
id={NavTab.Chats}
|
||||
label={i18n('icu:NavTabs__ItemLabel--Chats')}
|
||||
iconClassName="NavTabs__ItemIcon--Chats"
|
||||
navTabClassName="NavTabs__Item--Chats"
|
||||
unreadStats={unreadConversationsStats}
|
||||
/>
|
||||
<NavTabsItem
|
||||
|
@ -266,6 +273,7 @@ export function NavTabs({
|
|||
id={NavTab.Calls}
|
||||
label={i18n('icu:NavTabs__ItemLabel--Calls')}
|
||||
iconClassName="NavTabs__ItemIcon--Calls"
|
||||
navTabClassName="NavTabs__Item--Calls"
|
||||
unreadStats={{
|
||||
unreadCount: unreadCallsCount,
|
||||
unreadMentionsCount: 0,
|
||||
|
@ -279,6 +287,7 @@ export function NavTabs({
|
|||
label={i18n('icu:NavTabs__ItemLabel--Stories')}
|
||||
iconClassName="NavTabs__ItemIcon--Stories"
|
||||
hasError={hasFailedStorySends}
|
||||
navTabClassName="NavTabs__Item--Stories"
|
||||
unreadStats={{
|
||||
unreadCount: unreadStoriesCount,
|
||||
unreadMentionsCount: 0,
|
||||
|
@ -286,75 +295,21 @@ export function NavTabs({
|
|||
}}
|
||||
/>
|
||||
)}
|
||||
</TabList>
|
||||
<div className="NavTabs__Misc">
|
||||
<ContextMenu
|
||||
<NavTabsItem
|
||||
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',
|
||||
id={NavTab.Settings}
|
||||
label={i18n('icu:NavTabs__ItemLabel--Settings')}
|
||||
iconClassName="NavTabs__ItemIcon--Settings"
|
||||
navTabClassName="NavTabs__Item--Settings"
|
||||
unreadStats={{
|
||||
unreadCount: unreadCallsCount,
|
||||
unreadMentionsCount: 0,
|
||||
markedUnread: false,
|
||||
}}
|
||||
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>
|
||||
|
||||
</TabList>
|
||||
<div className="NavTabs__Misc">
|
||||
<button
|
||||
type="button"
|
||||
className="NavTabs__Item NavTabs__Item--Profile"
|
||||
|
@ -402,6 +357,9 @@ export function NavTabs({
|
|||
<TabPanel id={NavTab.Stories} className="NavTabs__TabPanel">
|
||||
{renderStoriesTab}
|
||||
</TabPanel>
|
||||
<TabPanel id={NavTab.Settings} className="NavTabs__TabPanel">
|
||||
{renderSettingsTab}
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -5,13 +5,17 @@ import type { Meta, StoryFn } from '@storybook/react';
|
|||
import React from 'react';
|
||||
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import type { PropsType } from './Preferences';
|
||||
import { Page, Preferences } from './Preferences';
|
||||
import { DEFAULT_CONVERSATION_COLOR } from '../types/Colors';
|
||||
import { PhoneNumberSharingMode } from '../util/phoneNumberSharingMode';
|
||||
import { PhoneNumberDiscoverability } from '../util/phoneNumberDiscoverability';
|
||||
import { EmojiSkinTone } from './fun/data/emojis';
|
||||
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;
|
||||
|
||||
|
@ -65,6 +69,26 @@ const exportLocalBackupResult = {
|
|||
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 {
|
||||
title: 'Components/Preferences',
|
||||
component: Preferences,
|
||||
|
@ -123,6 +147,7 @@ export default {
|
|||
hasMinimizeToSystemTray: true,
|
||||
hasNotificationAttention: false,
|
||||
hasNotifications: true,
|
||||
hasPendingUpdate: false,
|
||||
hasReadReceipts: true,
|
||||
hasRelayCalls: false,
|
||||
hasSpellCheck: true,
|
||||
|
@ -140,6 +165,7 @@ export default {
|
|||
isContentProtectionSupported: true,
|
||||
isContentProtectionNeeded: true,
|
||||
isMinimizeToAndStartInSystemTraySupported: true,
|
||||
isUpdateDownloaded: false,
|
||||
lastSyncTime: Date.now(),
|
||||
localeOverride: null,
|
||||
notificationContent: 'name',
|
||||
|
@ -156,12 +182,11 @@ export default {
|
|||
whoCanSeeMe: PhoneNumberSharingMode.Everybody,
|
||||
zoomFactor: 1,
|
||||
|
||||
getConversationsWithCustomColor: () => Promise.resolve([]),
|
||||
renderUpdateDialog,
|
||||
getConversationsWithCustomColor: () => [],
|
||||
|
||||
addCustomColor: action('addCustomColor'),
|
||||
closeSettings: action('closeSettings'),
|
||||
doDeleteAllData: action('doDeleteAllData'),
|
||||
doneRendering: action('doneRendering'),
|
||||
editCustomColor: action('editCustomColor'),
|
||||
exportLocalBackup: async () => {
|
||||
return {
|
||||
|
@ -211,6 +236,7 @@ export default {
|
|||
onSelectedSpeakerChange: action('onSelectedSpeakerChange'),
|
||||
onSentMediaQualityChange: action('onSentMediaQualityChange'),
|
||||
onSpellCheckChange: action('onSpellCheckChange'),
|
||||
onStartUpdate: action('onStartUpdate'),
|
||||
onTextFormattingChange: action('onTextFormattingChange'),
|
||||
onThemeChange: action('onThemeChange'),
|
||||
onUniversalExpireTimerChange: action('onUniversalExpireTimerChange'),
|
||||
|
@ -350,3 +376,13 @@ Internal.args = {
|
|||
initialPage: Page.Internal,
|
||||
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,
|
||||
useId,
|
||||
} from 'react';
|
||||
import { noop, partition } from 'lodash';
|
||||
import { isNumber, noop, partition } from 'lodash';
|
||||
import classNames from 'classnames';
|
||||
import * as LocaleMatcher from '@formatjs/intl-localematcher';
|
||||
import type { MediaDeviceSettings } from '../types/Calling';
|
||||
|
@ -53,7 +53,6 @@ import {
|
|||
format as formatExpirationTimer,
|
||||
} from '../util/expirationTimer';
|
||||
import { DurationInSeconds } from '../util/durations';
|
||||
import { useEscapeHandling } from '../hooks/useEscapeHandling';
|
||||
import { focusableSelector } from '../util/focusableSelectors';
|
||||
import { Modal } from './Modal';
|
||||
import { SearchInput } from './SearchInput';
|
||||
|
@ -93,24 +92,24 @@ export type PropsDataType = {
|
|||
hasAudioNotifications?: boolean;
|
||||
hasAutoConvertEmoji: boolean;
|
||||
hasAutoDownloadUpdate: boolean;
|
||||
hasAutoLaunch: boolean;
|
||||
hasAutoLaunch: boolean | undefined;
|
||||
hasCallNotifications: boolean;
|
||||
hasCallRingtoneNotification: boolean;
|
||||
hasContentProtection: boolean;
|
||||
hasContentProtection: boolean | undefined;
|
||||
hasCountMutedConversations: boolean;
|
||||
hasHideMenuBar?: boolean;
|
||||
hasIncomingCallNotifications: boolean;
|
||||
hasLinkPreviews: boolean;
|
||||
hasMediaCameraPermissions: boolean | undefined;
|
||||
hasMediaPermissions: boolean;
|
||||
hasMediaPermissions: boolean | undefined;
|
||||
hasMessageAudio: boolean;
|
||||
hasMinimizeToAndStartInSystemTray: boolean;
|
||||
hasMinimizeToSystemTray: boolean;
|
||||
hasMinimizeToAndStartInSystemTray: boolean | undefined;
|
||||
hasMinimizeToSystemTray: boolean | undefined;
|
||||
hasNotificationAttention: boolean;
|
||||
hasNotifications: boolean;
|
||||
hasReadReceipts: boolean;
|
||||
hasRelayCalls?: boolean;
|
||||
hasSpellCheck: boolean;
|
||||
hasSpellCheck: boolean | undefined;
|
||||
hasStoriesDisabled: boolean;
|
||||
hasTextFormatting: boolean;
|
||||
hasTypingIndicators: boolean;
|
||||
|
@ -122,51 +121,56 @@ export type PropsDataType = {
|
|||
selectedMicrophone?: AudioDevice;
|
||||
selectedSpeaker?: AudioDevice;
|
||||
sentMediaQualitySetting: SentMediaQualitySettingType;
|
||||
themeSetting: ThemeSettingType;
|
||||
themeSetting: ThemeSettingType | undefined;
|
||||
universalExpireTimer: DurationInSeconds;
|
||||
whoCanFindMe: PhoneNumberDiscoverability;
|
||||
whoCanSeeMe: PhoneNumberSharingMode;
|
||||
zoomFactor: ZoomFactorType;
|
||||
zoomFactor: ZoomFactorType | undefined;
|
||||
|
||||
// Localization
|
||||
availableLocales: ReadonlyArray<string>;
|
||||
localeOverride: string | null;
|
||||
localeOverride: string | null | undefined;
|
||||
preferredSystemLocales: ReadonlyArray<string>;
|
||||
resolvedLocale: string;
|
||||
|
||||
// Other props
|
||||
hasPendingUpdate: boolean;
|
||||
initialSpellCheckSetting: boolean;
|
||||
isUpdateDownloaded: boolean;
|
||||
|
||||
// Limited support features
|
||||
isAutoDownloadUpdatesSupported: boolean;
|
||||
isAutoLaunchSupported: boolean;
|
||||
isContentProtectionNeeded: boolean;
|
||||
isContentProtectionSupported: boolean;
|
||||
isHideMenuBarSupported: boolean;
|
||||
isNotificationAttentionSupported: boolean;
|
||||
isSyncSupported: boolean;
|
||||
isSystemTraySupported: boolean;
|
||||
isMinimizeToAndStartInSystemTraySupported: boolean;
|
||||
isInternalUser: boolean;
|
||||
isContentProtectionNeeded: boolean;
|
||||
isContentProtectionSupported: boolean;
|
||||
|
||||
// Devices
|
||||
availableCameras: Array<
|
||||
Pick<MediaDeviceInfo, 'deviceId' | 'groupId' | 'kind' | 'label'>
|
||||
>;
|
||||
} & Omit<MediaDeviceSettings, 'availableCameras'>;
|
||||
|
||||
type PropsFunctionType = {
|
||||
// Render props
|
||||
renderUpdateDialog: (
|
||||
_: Readonly<{ containerWidthBreakpoint: WidthBreakpoint }>
|
||||
) => JSX.Element;
|
||||
|
||||
// Other props
|
||||
addCustomColor: (color: CustomColorType) => unknown;
|
||||
closeSettings: () => unknown;
|
||||
doDeleteAllData: () => unknown;
|
||||
doneRendering: () => unknown;
|
||||
editCustomColor: (colorId: string, color: CustomColorType) => unknown;
|
||||
exportLocalBackup: () => Promise<BackupValidationResultType>;
|
||||
getConversationsWithCustomColor: (
|
||||
colorId: string
|
||||
) => Promise<Array<ConversationType>>;
|
||||
getConversationsWithCustomColor: (colorId: string) => Array<ConversationType>;
|
||||
importLocalBackup: () => Promise<ValidateLocalBackupStructureResultType>;
|
||||
makeSyncRequest: () => unknown;
|
||||
onStartUpdate: () => unknown;
|
||||
refreshCloudBackupStatus: () => void;
|
||||
refreshBackupSubscriptionStatus: () => void;
|
||||
removeCustomColor: (colorId: string) => unknown;
|
||||
|
@ -199,7 +203,7 @@ type PropsFunctionType = {
|
|||
onHideMenuBarChange: CheckboxChangeHandlerType;
|
||||
onIncomingCallNotificationsChange: CheckboxChangeHandlerType;
|
||||
onLastSyncTimeChange: (time: number) => unknown;
|
||||
onLocaleChange: (locale: string | null) => void;
|
||||
onLocaleChange: (locale: string | null | undefined) => void;
|
||||
onMediaCameraPermissionsChange: CheckboxChangeHandlerType;
|
||||
onMediaPermissionsChange: CheckboxChangeHandlerType;
|
||||
onMessageAudioChange: CheckboxChangeHandlerType;
|
||||
|
@ -284,13 +288,11 @@ export function Preferences({
|
|||
backupFeatureEnabled,
|
||||
backupSubscriptionStatus,
|
||||
blockedCount,
|
||||
closeSettings,
|
||||
cloudBackupStatus,
|
||||
customColors,
|
||||
defaultConversationColor,
|
||||
deviceName = '',
|
||||
doDeleteAllData,
|
||||
doneRendering,
|
||||
editCustomColor,
|
||||
emojiSkinToneDefault,
|
||||
exportLocalBackup,
|
||||
|
@ -313,6 +315,7 @@ export function Preferences({
|
|||
hasMinimizeToSystemTray,
|
||||
hasNotificationAttention,
|
||||
hasNotifications,
|
||||
hasPendingUpdate,
|
||||
hasReadReceipts,
|
||||
hasRelayCalls,
|
||||
hasSpellCheck,
|
||||
|
@ -325,14 +328,15 @@ export function Preferences({
|
|||
initialSpellCheckSetting,
|
||||
isAutoDownloadUpdatesSupported,
|
||||
isAutoLaunchSupported,
|
||||
isContentProtectionNeeded,
|
||||
isContentProtectionSupported,
|
||||
isHideMenuBarSupported,
|
||||
isNotificationAttentionSupported,
|
||||
isSyncSupported,
|
||||
isSystemTraySupported,
|
||||
isMinimizeToAndStartInSystemTraySupported,
|
||||
isInternalUser,
|
||||
isContentProtectionNeeded,
|
||||
isContentProtectionSupported,
|
||||
isUpdateDownloaded,
|
||||
lastSyncTime,
|
||||
makeSyncRequest,
|
||||
notificationContent,
|
||||
|
@ -377,6 +381,7 @@ export function Preferences({
|
|||
refreshBackupSubscriptionStatus,
|
||||
removeCustomColor,
|
||||
removeCustomColorOnConversations,
|
||||
renderUpdateDialog,
|
||||
resetAllChatColors,
|
||||
resetDefaultChatColor,
|
||||
resolvedLocale,
|
||||
|
@ -411,7 +416,7 @@ export function Preferences({
|
|||
null
|
||||
);
|
||||
const [selectedLanguageLocale, setSelectedLanguageLocale] = useState<
|
||||
string | null
|
||||
string | null | undefined
|
||||
>(localeOverride);
|
||||
const [languageSearchInput, setLanguageSearchInput] = useState('');
|
||||
const [toast, setToast] = useState<AnyToast | undefined>();
|
||||
|
@ -432,6 +437,13 @@ export function Preferences({
|
|||
setPage(Page.General);
|
||||
}
|
||||
|
||||
let maybeUpdateDialog: JSX.Element | undefined;
|
||||
if (hasPendingUpdate || isUpdateDownloaded) {
|
||||
maybeUpdateDialog = renderUpdateDialog({
|
||||
containerWidthBreakpoint: WidthBreakpoint.Wide,
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (page === Page.Backups) {
|
||||
refreshCloudBackupStatus();
|
||||
|
@ -439,18 +451,6 @@ export function Preferences({
|
|||
}
|
||||
}, [page, refreshCloudBackupStatus, refreshBackupSubscriptionStatus]);
|
||||
|
||||
useEffect(() => {
|
||||
doneRendering();
|
||||
}, [doneRendering]);
|
||||
|
||||
useEscapeHandling(() => {
|
||||
if (languageDialog != null) {
|
||||
closeLanguageDialog();
|
||||
} else {
|
||||
closeSettings();
|
||||
}
|
||||
});
|
||||
|
||||
const onZoomSelectChange = useCallback(
|
||||
(value: string) => {
|
||||
const number = parseFloat(value);
|
||||
|
@ -631,6 +631,7 @@ export function Preferences({
|
|||
{isAutoLaunchSupported && (
|
||||
<Checkbox
|
||||
checked={hasAutoLaunch}
|
||||
disabled={hasAutoLaunch === undefined}
|
||||
label={i18n('icu:autoLaunchDescription')}
|
||||
moduleClassName="Preferences__checkbox"
|
||||
name="autoLaunch"
|
||||
|
@ -650,6 +651,7 @@ export function Preferences({
|
|||
<>
|
||||
<Checkbox
|
||||
checked={hasMinimizeToSystemTray}
|
||||
disabled={hasMinimizeToSystemTray === undefined}
|
||||
label={i18n('icu:SystemTraySetting__minimize-to-system-tray')}
|
||||
moduleClassName="Preferences__checkbox"
|
||||
name="system-tray-setting-minimize-to-system-tray"
|
||||
|
@ -658,7 +660,10 @@ export function Preferences({
|
|||
{isMinimizeToAndStartInSystemTraySupported && (
|
||||
<Checkbox
|
||||
checked={hasMinimizeToAndStartInSystemTray}
|
||||
disabled={!hasMinimizeToSystemTray}
|
||||
disabled={
|
||||
!hasMinimizeToSystemTray ||
|
||||
hasMinimizeToAndStartInSystemTray === undefined
|
||||
}
|
||||
label={i18n(
|
||||
'icu:SystemTraySetting__minimize-to-and-start-in-system-tray'
|
||||
)}
|
||||
|
@ -673,6 +678,7 @@ export function Preferences({
|
|||
<SettingsRow title={i18n('icu:permissions')}>
|
||||
<Checkbox
|
||||
checked={hasMediaPermissions}
|
||||
disabled={hasMediaPermissions === undefined}
|
||||
label={i18n('icu:mediaPermissionsDescription')}
|
||||
moduleClassName="Preferences__checkbox"
|
||||
name="mediaPermissions"
|
||||
|
@ -680,6 +686,7 @@ export function Preferences({
|
|||
/>
|
||||
<Checkbox
|
||||
checked={hasMediaCameraPermissions ?? false}
|
||||
disabled={hasMediaCameraPermissions === undefined}
|
||||
label={i18n('icu:mediaCameraPermissionsDescription')}
|
||||
moduleClassName="Preferences__checkbox"
|
||||
name="mediaCameraPermissions"
|
||||
|
@ -702,7 +709,10 @@ export function Preferences({
|
|||
} else if (page === Page.Appearance) {
|
||||
let zoomFactors = DEFAULT_ZOOM_FACTORS;
|
||||
|
||||
if (!zoomFactors.some(({ value }) => value === zoomFactor)) {
|
||||
if (
|
||||
isNumber(zoomFactor) &&
|
||||
!zoomFactors.some(({ value }) => value === zoomFactor)
|
||||
) {
|
||||
zoomFactors = [
|
||||
...zoomFactors,
|
||||
{
|
||||
|
@ -711,6 +721,13 @@ export function Preferences({
|
|||
},
|
||||
].sort((a, b) => a.value - b.value);
|
||||
}
|
||||
let localeText = '';
|
||||
if (localeOverride !== undefined) {
|
||||
localeText =
|
||||
localeOverride != null
|
||||
? getLocaleDisplayName(resolvedLocale, localeOverride)
|
||||
: i18n('icu:Preferences__Language__SystemLanguage');
|
||||
}
|
||||
|
||||
settings = (
|
||||
<>
|
||||
|
@ -728,12 +745,14 @@ export function Preferences({
|
|||
className="Preferences__LanguageButton"
|
||||
lang={localeOverride ?? resolvedLocale}
|
||||
>
|
||||
{localeOverride != null
|
||||
? getLocaleDisplayName(resolvedLocale, localeOverride)
|
||||
: i18n('icu:Preferences__Language__SystemLanguage')}
|
||||
{localeText}
|
||||
</span>
|
||||
}
|
||||
onClick={() => {
|
||||
// We haven't loaded the user's setting yet
|
||||
if (localeOverride === undefined) {
|
||||
return;
|
||||
}
|
||||
setLanguageDialog(LanguageDialog.Selection);
|
||||
}}
|
||||
/>
|
||||
|
@ -852,6 +871,7 @@ export function Preferences({
|
|||
right={
|
||||
<Select
|
||||
id={themeSelectId}
|
||||
disabled={themeSetting === undefined}
|
||||
onChange={onThemeChange}
|
||||
options={[
|
||||
{
|
||||
|
@ -898,8 +918,9 @@ export function Preferences({
|
|||
right={
|
||||
<Select
|
||||
id={zoomSelectId}
|
||||
disabled={zoomFactor === undefined}
|
||||
onChange={onZoomSelectChange}
|
||||
options={zoomFactors}
|
||||
options={zoomFactor === undefined ? [] : zoomFactors}
|
||||
value={zoomFactor}
|
||||
/>
|
||||
}
|
||||
|
@ -909,7 +930,10 @@ export function Preferences({
|
|||
);
|
||||
} else if (page === Page.Chats) {
|
||||
let spellCheckDirtyText: string | undefined;
|
||||
if (initialSpellCheckSetting !== hasSpellCheck) {
|
||||
if (
|
||||
hasSpellCheck !== undefined &&
|
||||
initialSpellCheckSetting !== hasSpellCheck
|
||||
) {
|
||||
spellCheckDirtyText = hasSpellCheck
|
||||
? i18n('icu:spellCheckWillBeEnabled')
|
||||
: i18n('icu:spellCheckWillBeDisabled');
|
||||
|
@ -927,6 +951,7 @@ export function Preferences({
|
|||
<SettingsRow title={i18n('icu:Preferences__button--chats')}>
|
||||
<Checkbox
|
||||
checked={hasSpellCheck}
|
||||
disabled={hasSpellCheck === undefined}
|
||||
description={spellCheckDirtyText}
|
||||
label={i18n('icu:spellCheckDescription')}
|
||||
moduleClassName="Preferences__checkbox"
|
||||
|
@ -1390,6 +1415,7 @@ export function Preferences({
|
|||
<SettingsRow title={i18n('icu:Preferences__Privacy__Application')}>
|
||||
<Checkbox
|
||||
checked={hasContentProtection}
|
||||
disabled={hasContentProtection === undefined}
|
||||
description={i18n(
|
||||
'icu:Preferences__content-protection--description'
|
||||
)}
|
||||
|
@ -1811,6 +1837,13 @@ export function Preferences({
|
|||
<div className="module-title-bar-drag-area" />
|
||||
<div className="Preferences">
|
||||
<div className="Preferences__page-selector">
|
||||
<h1 className="Preferences__header">
|
||||
{i18n('icu:Preferences--header')}
|
||||
</h1>
|
||||
{maybeUpdateDialog ? (
|
||||
<div className="module-left-pane__dialogs">{maybeUpdateDialog}</div>
|
||||
) : null}
|
||||
<div className="Preferences__scroll-area">
|
||||
<button
|
||||
type="button"
|
||||
className={classNames({
|
||||
|
@ -1867,7 +1900,6 @@ export function Preferences({
|
|||
>
|
||||
{i18n('icu:Preferences__button--notifications')}
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className={classNames({
|
||||
|
@ -1880,7 +1912,6 @@ export function Preferences({
|
|||
>
|
||||
{i18n('icu:Preferences__button--privacy')}
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className={classNames({
|
||||
|
@ -1919,9 +1950,12 @@ export function Preferences({
|
|||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="Preferences__settings-pane-spacer" />
|
||||
<div className="Preferences__settings-pane" ref={settingsPaneRef}>
|
||||
{settings}
|
||||
</div>
|
||||
<div className="Preferences__settings-pane-spacer" />
|
||||
</div>
|
||||
<ToastManager
|
||||
OS="unused"
|
||||
|
|
|
@ -5,16 +5,13 @@ import type { BrowserWindow } from 'electron';
|
|||
import { ipcMain as ipc, session } from 'electron';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
import * as log from '../logging/log';
|
||||
import { userConfig } from '../../app/user_config';
|
||||
import { ephemeralConfig } from '../../app/ephemeral_config';
|
||||
import { installPermissionsHandler } from '../../app/permissions';
|
||||
import { strictAssert } from '../util/assert';
|
||||
import { explodePromise } from '../util/explodePromise';
|
||||
import type {
|
||||
IPCEventsValuesType,
|
||||
IPCEventsCallbacksType,
|
||||
} from '../util/createIPCEvents';
|
||||
import type { EphemeralSettings, SettingsValuesType } from '../util/preload';
|
||||
|
||||
import type { EphemeralSettings } from '../util/preload';
|
||||
|
||||
const EPHEMERAL_NAME_MAP = new Map([
|
||||
['spellCheck', 'spell-check'],
|
||||
|
@ -24,18 +21,8 @@ const EPHEMERAL_NAME_MAP = new Map([
|
|||
['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 {
|
||||
#mainWindow?: BrowserWindow;
|
||||
readonly #responseQueue = new Map<number, ResponseQueueEntry>();
|
||||
#responseSeq = 0;
|
||||
|
||||
public setMainWindow(mainWindow: BrowserWindow | undefined): void {
|
||||
this.#mainWindow = mainWindow;
|
||||
|
@ -45,81 +32,16 @@ export class SettingsChannel extends EventEmitter {
|
|||
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 {
|
||||
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('systemTraySetting');
|
||||
this.#installEphemeralSetting('localeOverride');
|
||||
|
@ -156,113 +78,6 @@ export class SettingsChannel extends EventEmitter {
|
|||
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>(
|
||||
|
@ -285,8 +100,6 @@ export class SettingsChannel extends EventEmitter {
|
|||
);
|
||||
ephemeralConfig.set(ephemeralName, value);
|
||||
|
||||
this.emit(`change:${name}`, value);
|
||||
|
||||
// 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
|
||||
// to main the event 'preferences-changed'.
|
||||
|
@ -313,12 +126,6 @@ export class SettingsChannel extends EventEmitter {
|
|||
callback: (name: string) => void
|
||||
): 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(
|
||||
type: string | symbol,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
@ -337,12 +144,6 @@ export class SettingsChannel extends EventEmitter {
|
|||
name: string
|
||||
): 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
|
||||
public override emit(type: string | symbol, ...args: Array<any>): boolean {
|
||||
return super.emit(type, ...args);
|
||||
|
|
|
@ -34,6 +34,7 @@ import {
|
|||
import { drop } from '../util/drop';
|
||||
import { getMessageById } from '../messages/getMessageById';
|
||||
import { MessageModel } from '../models/messages';
|
||||
import { areStoryViewReceiptsEnabled } from '../types/Stories';
|
||||
|
||||
const { deleteSentProtoRecipient, removeSyncTasks, removeSyncTaskById } =
|
||||
DataWriter;
|
||||
|
@ -398,7 +399,7 @@ const shouldDropReceipt = (
|
|||
return !window.storage.get('read-receipt-setting');
|
||||
case messageReceiptTypeSchema.Enum.View:
|
||||
if (isStory(message)) {
|
||||
return !window.Events.getStoryViewReceiptsEnabled();
|
||||
return !areStoryViewReceiptsEnabled();
|
||||
}
|
||||
return !window.storage.get('read-receipt-setting');
|
||||
default:
|
||||
|
|
|
@ -193,6 +193,7 @@ import { cleanupMessages } from '../util/cleanup';
|
|||
import { MessageModel } from './messages';
|
||||
import { applyNewAvatar } from '../groups';
|
||||
import { safeSetTimeout } from '../util/timeout';
|
||||
import { getTypingIndicatorSetting } from '../types/Util';
|
||||
import { INITIAL_EXPIRE_TIMER_VERSION } from '../util/expirationTimer';
|
||||
|
||||
/* eslint-disable more/no-then */
|
||||
|
@ -1125,7 +1126,7 @@ export class ConversationModel extends window.Backbone
|
|||
|
||||
bumpTyping(): void {
|
||||
// We don't send typing messages if the setting is disabled
|
||||
if (!window.Events.getTypingIndicatorSetting()) {
|
||||
if (!getTypingIndicatorSetting()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ function _maybeGrabLinkPreview(
|
|||
): void {
|
||||
// Don't generate link previews if user has turned them off. When posting a
|
||||
// story we should return minimal (url-only) link previews.
|
||||
if (!window.Events.getLinkPreviewSetting() && mode === 'conversation') {
|
||||
if (!LinkPreview.getLinkPreviewSetting() && mode === 'conversation') {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -102,7 +102,7 @@ function _maybeGrabLinkPreview(
|
|||
drop(
|
||||
addLinkPreview(link, source, {
|
||||
conversationId,
|
||||
disableFetch: !window.Events.getLinkPreviewSetting(),
|
||||
disableFetch: !LinkPreview.getLinkPreviewSetting(),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
@ -153,6 +153,8 @@ import { generateBackupsSubscriberData } from '../../util/backupSubscriptionData
|
|||
import { getEnvironment, isTestEnvironment } from '../../environment';
|
||||
import { calculateLightness } from '../../util/getHSL';
|
||||
import { toDayOfWeekArray } from '../../types/NotificationProfile';
|
||||
import { getLinkPreviewSetting } from '../../types/LinkPreview';
|
||||
import { getTypingIndicatorSetting } from '../../types/Util';
|
||||
|
||||
const MAX_CONCURRENCY = 10;
|
||||
|
||||
|
@ -864,8 +866,8 @@ export class BackupExportStream extends Readable {
|
|||
accountSettings: {
|
||||
readReceipts: storage.get('read-receipt-setting'),
|
||||
sealedSenderIndicators: storage.get('sealedSenderIndicators'),
|
||||
typingIndicators: window.Events.getTypingIndicatorSetting(),
|
||||
linkPreviews: window.Events.getLinkPreviewSetting(),
|
||||
typingIndicators: getTypingIndicatorSetting(),
|
||||
linkPreviews: getLinkPreviewSetting(),
|
||||
notDiscoverableByPhoneNumber:
|
||||
parsePhoneNumberDiscoverability(
|
||||
storage.get('phoneNumberDiscoverability')
|
||||
|
|
|
@ -235,6 +235,38 @@ export type SetPresentingOptionsType = Readonly<{
|
|||
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 {
|
||||
if (!name || name.length <= 4) {
|
||||
return name;
|
||||
|
@ -2653,7 +2685,7 @@ export class CallingClass {
|
|||
const { availableCameras, availableMicrophones, availableSpeakers } =
|
||||
await this.getAvailableIODevices();
|
||||
|
||||
const preferredMicrophone = window.Events.getPreferredAudioInputDevice();
|
||||
const preferredMicrophone = getPreferredAudioInputDevice();
|
||||
const selectedMicIndex = findBestMatchingAudioDeviceIndex(
|
||||
{
|
||||
available: availableMicrophones,
|
||||
|
@ -2666,7 +2698,7 @@ export class CallingClass {
|
|||
? availableMicrophones[selectedMicIndex]
|
||||
: undefined;
|
||||
|
||||
const preferredSpeaker = window.Events.getPreferredAudioOutputDevice();
|
||||
const preferredSpeaker = getPreferredAudioOutputDevice();
|
||||
const selectedSpeakerIndex = findBestMatchingAudioDeviceIndex(
|
||||
{
|
||||
available: availableSpeakers,
|
||||
|
@ -2679,7 +2711,7 @@ export class CallingClass {
|
|||
? availableSpeakers[selectedSpeakerIndex]
|
||||
: undefined;
|
||||
|
||||
const preferredCamera = window.Events.getPreferredVideoInputDevice();
|
||||
const preferredCamera = getPreferredVideoInputDevice();
|
||||
const selectedCamera = findBestMatchingCameraId(
|
||||
availableCameras,
|
||||
preferredCamera
|
||||
|
@ -2701,7 +2733,7 @@ export class CallingClass {
|
|||
device.index,
|
||||
truncateForLogging(device.name)
|
||||
);
|
||||
void window.Events.setPreferredAudioInputDevice(device);
|
||||
drop(setPreferredAudioInputDevice(device));
|
||||
RingRTC.setAudioInput(device.index);
|
||||
}
|
||||
|
||||
|
@ -2711,7 +2743,7 @@ export class CallingClass {
|
|||
device.index,
|
||||
truncateForLogging(device.name)
|
||||
);
|
||||
void window.Events.setPreferredAudioOutputDevice(device);
|
||||
drop(setPreferredAudioOutputDevice(device));
|
||||
RingRTC.setAudioOutput(device.index);
|
||||
}
|
||||
|
||||
|
@ -2749,7 +2781,7 @@ export class CallingClass {
|
|||
|
||||
async setPreferredCamera(device: string): Promise<void> {
|
||||
log.info('MediaDevice: setPreferredCamera', device);
|
||||
void window.Events.setPreferredVideoInputDevice(device);
|
||||
drop(setPreferredVideoInputDevice(device));
|
||||
await this.#videoCapturer.setPreferredDevice(device);
|
||||
}
|
||||
|
||||
|
@ -2760,7 +2792,7 @@ export class CallingClass {
|
|||
const logId = `CallingClass.handleCallingMessage(${envelope.timestamp})`;
|
||||
log.info(logId);
|
||||
|
||||
const enableIncomingCalls = window.Events.getIncomingCallNotification();
|
||||
const enableIncomingCalls = getIncomingCallNotification();
|
||||
if (callingMessage.offer && !enableIncomingCalls) {
|
||||
// Drop offers silently if incoming call notifications are disabled.
|
||||
log.info(`${logId}: Incoming calls are disabled, ignoring call offer.`);
|
||||
|
@ -3078,7 +3110,7 @@ export class CallingClass {
|
|||
RingRTC.cancelGroupRing(groupIdBytes, ringId, null);
|
||||
} else if (this.#areAnyCallsActiveOrRinging()) {
|
||||
RingRTC.cancelGroupRing(groupIdBytes, ringId, RingCancelReason.Busy);
|
||||
} else if (window.Events.getIncomingCallNotification()) {
|
||||
} else if (getIncomingCallNotification()) {
|
||||
shouldRing = true;
|
||||
} else {
|
||||
log.info(
|
||||
|
@ -3633,7 +3665,7 @@ export class CallingClass {
|
|||
return false;
|
||||
}
|
||||
|
||||
const shouldRelayCalls = window.Events.getAlwaysRelayCalls();
|
||||
const shouldRelayCalls = getAlwaysRelayCalls();
|
||||
|
||||
// If the peer is not a Signal Connection, force IP hiding.
|
||||
const isContactUntrusted = !isSignalConnection(conversation.attributes);
|
||||
|
|
|
@ -84,6 +84,12 @@ import {
|
|||
generateBackupsSubscriberData,
|
||||
saveBackupsSubscriberData,
|
||||
} from '../util/backupSubscriptionData';
|
||||
import { getLinkPreviewSetting } from '../types/LinkPreview';
|
||||
import {
|
||||
getReadReceiptSetting,
|
||||
getSealedSenderIndicatorSetting,
|
||||
getTypingIndicatorSetting,
|
||||
} from '../types/Util';
|
||||
|
||||
const MY_STORY_BYTES = uuidToBytes(MY_STORY_ID);
|
||||
|
||||
|
@ -338,14 +344,10 @@ export function toAccountRecord(
|
|||
accountRecord.noteToSelfMarkedUnread = Boolean(
|
||||
conversation.get('markedUnread')
|
||||
);
|
||||
accountRecord.readReceipts = Boolean(window.Events.getReadReceiptSetting());
|
||||
accountRecord.sealedSenderIndicators = Boolean(
|
||||
window.storage.get('sealedSenderIndicators')
|
||||
);
|
||||
accountRecord.typingIndicators = Boolean(
|
||||
window.Events.getTypingIndicatorSetting()
|
||||
);
|
||||
accountRecord.linkPreviews = Boolean(window.Events.getLinkPreviewSetting());
|
||||
accountRecord.readReceipts = getReadReceiptSetting();
|
||||
accountRecord.sealedSenderIndicators = getSealedSenderIndicatorSetting();
|
||||
accountRecord.typingIndicators = getTypingIndicatorSetting();
|
||||
accountRecord.linkPreviews = getLinkPreviewSetting();
|
||||
|
||||
const preferContactAvatars = window.storage.get('preferContactAvatars');
|
||||
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 linkPreviews } from './ducks/linkPreviews';
|
||||
import { actions as mediaGallery } from './ducks/mediaGallery';
|
||||
import { actions as nav } from './ducks/nav';
|
||||
import { actions as network } from './ducks/network';
|
||||
import { actions as notificationProfiles } from './ducks/notificationProfiles';
|
||||
import { actions as safetyNumber } from './ducks/safetyNumber';
|
||||
|
@ -55,6 +56,7 @@ export const actionCreators: ReduxActions = {
|
|||
lightbox,
|
||||
linkPreviews,
|
||||
mediaGallery,
|
||||
nav,
|
||||
network,
|
||||
notificationProfiles,
|
||||
safetyNumber,
|
||||
|
|
|
@ -11,6 +11,7 @@ export enum NavTab {
|
|||
Chats = 'Chats',
|
||||
Calls = 'Calls',
|
||||
Stories = 'Stories',
|
||||
Settings = 'Settings',
|
||||
}
|
||||
|
||||
// State
|
||||
|
|
|
@ -29,7 +29,11 @@ import { DataReader, DataWriter } from '../../sql/Client';
|
|||
import { ReadStatus } from '../../messages/MessageReadStatus';
|
||||
import { SendStatus } from '../../messages/MessageSendState';
|
||||
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 { drop } from '../../util/drop';
|
||||
import { blockSendUntilConversationsAreVerified } from '../../util/blockSendUntilConversationsAreVerified';
|
||||
|
@ -51,6 +55,7 @@ import {
|
|||
getStories,
|
||||
getStoryDownloadableAttachment,
|
||||
} from '../selectors/stories';
|
||||
import { setStoriesDisabled as utilSetStoriesDisabled } from '../../util/stories';
|
||||
import { getStoryDataFromMessageAttributes } from '../../services/storyLoader';
|
||||
import { isGroup } from '../../util/whatTypeOfConversation';
|
||||
import { isNotNil } from '../../util/isNotNil';
|
||||
|
@ -443,10 +448,7 @@ function markStoryRead(
|
|||
drop(viewSyncJobQueue.add({ viewSyncs }));
|
||||
}
|
||||
|
||||
if (
|
||||
!isSignalOnboardingStory &&
|
||||
window.Events.getStoryViewReceiptsEnabled()
|
||||
) {
|
||||
if (!isSignalOnboardingStory && areStoryViewReceiptsEnabled()) {
|
||||
drop(
|
||||
conversationJobQueue.add({
|
||||
type: conversationQueueJobEnum.enum.Receipts,
|
||||
|
@ -1380,7 +1382,7 @@ function setStoriesDisabled(
|
|||
value: boolean
|
||||
): ThunkAction<void, RootStateType, unknown, never> {
|
||||
return async () => {
|
||||
await window.Events.setHasStoriesDisabled(value);
|
||||
await utilSetStoriesDisabled(value);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -82,6 +82,7 @@ export function initializeRedux(data: ReduxInitData): void {
|
|||
actionCreators.mediaGallery,
|
||||
store.dispatch
|
||||
),
|
||||
nav: bindActionCreators(actionCreators.nav, store.dispatch),
|
||||
network: bindActionCreators(actionCreators.network, store.dispatch),
|
||||
notificationProfiles: bindActionCreators(
|
||||
actionCreators.notificationProfiles,
|
||||
|
|
|
@ -63,6 +63,9 @@ import { getActiveProfile } from '../selectors/notificationProfiles';
|
|||
function renderDeviceSelection(): JSX.Element {
|
||||
return <SmartCallingDeviceSelection />;
|
||||
}
|
||||
function getCallSystemNotification() {
|
||||
return window.storage.get('call-system-notification', true);
|
||||
}
|
||||
|
||||
const getGroupCallVideoFrameSource =
|
||||
callingService.getGroupCallVideoFrameSource.bind(callingService);
|
||||
|
@ -74,7 +77,7 @@ async function notifyForCall(
|
|||
): Promise<void> {
|
||||
const shouldNotify =
|
||||
!window.SignalContext.activeWindowService.isActive() &&
|
||||
window.Events.getCallSystemNotification();
|
||||
getCallSystemNotification();
|
||||
if (!shouldNotify) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@ export const SmartChatColorPicker = memo(function SmartChatColorPicker({
|
|||
};
|
||||
|
||||
const getConversationsWithCustomColor = useCallback(
|
||||
async (colorId: string): Promise<Array<ConversationType>> => {
|
||||
(colorId: string): Array<ConversationType> => {
|
||||
return conversationWithCustomColorSelector(colorId);
|
||||
},
|
||||
[conversationWithCustomColorSelector]
|
||||
|
|
|
@ -20,6 +20,7 @@ import {
|
|||
getInboxEnvelopeTimestamp,
|
||||
getInboxFirstEnvelopeTimestamp,
|
||||
} from '../selectors/inbox';
|
||||
import { SmartPreferences } from './Preferences';
|
||||
|
||||
function renderChatsTab() {
|
||||
return <SmartChatsTab />;
|
||||
|
@ -41,6 +42,10 @@ function renderStoriesTab() {
|
|||
return <SmartStoriesTab />;
|
||||
}
|
||||
|
||||
function renderSettingsTab() {
|
||||
return <SmartPreferences />;
|
||||
}
|
||||
|
||||
export const SmartInbox = memo(function SmartInbox(): JSX.Element {
|
||||
const i18n = useSelector(getIntl);
|
||||
const isCustomizingPreferredReactions = useSelector(
|
||||
|
@ -70,6 +75,7 @@ export const SmartInbox = memo(function SmartInbox(): JSX.Element {
|
|||
}
|
||||
renderNavTabs={renderNavTabs}
|
||||
renderStoriesTab={renderStoriesTab}
|
||||
renderSettingsTab={renderSettingsTab}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -15,9 +15,7 @@ import {
|
|||
getHasAnyFailedStorySends,
|
||||
getStoriesNotificationCount,
|
||||
} from '../selectors/stories';
|
||||
import { showSettings } from '../../shims/Whisper';
|
||||
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||
import { useUpdatesActions } from '../ducks/updates';
|
||||
import { getStoriesEnabled } from '../selectors/items';
|
||||
import { getSelectedNavTab } from '../selectors/nav';
|
||||
import type { NavTab } from '../ducks/nav';
|
||||
|
@ -31,6 +29,7 @@ export type SmartNavTabsProps = Readonly<{
|
|||
renderCallsTab: () => ReactNode;
|
||||
renderChatsTab: () => ReactNode;
|
||||
renderStoriesTab: () => ReactNode;
|
||||
renderSettingsTab: () => ReactNode;
|
||||
}>;
|
||||
|
||||
export const SmartNavTabs = memo(function SmartNavTabs({
|
||||
|
@ -39,6 +38,7 @@ export const SmartNavTabs = memo(function SmartNavTabs({
|
|||
renderCallsTab,
|
||||
renderChatsTab,
|
||||
renderStoriesTab,
|
||||
renderSettingsTab,
|
||||
}: SmartNavTabsProps): JSX.Element {
|
||||
const i18n = useSelector(getIntl);
|
||||
const selectedNavTab = useSelector(getSelectedNavTab);
|
||||
|
@ -54,7 +54,6 @@ export const SmartNavTabs = memo(function SmartNavTabs({
|
|||
const hasPendingUpdate = useSelector(getHasPendingUpdate);
|
||||
|
||||
const { toggleProfileEditor } = useGlobalModalActions();
|
||||
const { startUpdate } = useUpdatesActions();
|
||||
|
||||
const onNavTabSelected = useCallback(
|
||||
(tab: NavTab) => {
|
||||
|
@ -75,14 +74,13 @@ export const SmartNavTabs = memo(function SmartNavTabs({
|
|||
i18n={i18n}
|
||||
me={me}
|
||||
navTabsCollapsed={navTabsCollapsed}
|
||||
onShowSettings={showSettings}
|
||||
onStartUpdate={startUpdate}
|
||||
onNavTabSelected={onNavTabSelected}
|
||||
onToggleNavTabsCollapse={onToggleNavTabsCollapse}
|
||||
onToggleProfileEditor={toggleProfileEditor}
|
||||
renderCallsTab={renderCallsTab}
|
||||
renderChatsTab={renderChatsTab}
|
||||
renderStoriesTab={renderStoriesTab}
|
||||
renderSettingsTab={renderSettingsTab}
|
||||
selectedNavTab={selectedNavTab}
|
||||
storiesEnabled={storiesEnabled}
|
||||
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<{
|
||||
containerWidthBreakpoint: WidthBreakpoint;
|
||||
disableDismiss?: boolean;
|
||||
}>;
|
||||
|
||||
export const SmartUpdateDialog = memo(function SmartUpdateDialog({
|
||||
containerWidthBreakpoint,
|
||||
disableDismiss,
|
||||
}: SmartUpdateDialogProps) {
|
||||
const i18n = useSelector(getIntl);
|
||||
const { dismissDialog, snoozeUpdate, startUpdate } = useUpdatesActions();
|
||||
|
@ -37,6 +39,7 @@ export const SmartUpdateDialog = memo(function SmartUpdateDialog({
|
|||
version={version}
|
||||
currentVersion={window.getVersion()}
|
||||
dismissDialog={dismissDialog}
|
||||
disableDismiss={disableDismiss}
|
||||
snoozeUpdate={snoozeUpdate}
|
||||
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 linkPreviews } from './ducks/linkPreviews';
|
||||
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 notificationProfiles } from './ducks/notificationProfiles';
|
||||
import type { actions as safetyNumber } from './ducks/safetyNumber';
|
||||
|
@ -54,6 +55,7 @@ export type ReduxActions = {
|
|||
lightbox: typeof lightbox;
|
||||
linkPreviews: typeof linkPreviews;
|
||||
mediaGallery: typeof mediaGallery;
|
||||
nav: typeof nav;
|
||||
network: typeof network;
|
||||
notificationProfiles: typeof notificationProfiles;
|
||||
safetyNumber: typeof safetyNumber;
|
||||
|
|
|
@ -278,11 +278,4 @@ export async function setupBasics(): Promise<void> {
|
|||
systemGivenName: 'ME',
|
||||
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) {
|
||||
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) {
|
||||
this.sandbox.restore();
|
||||
|
||||
window.Events = oldEvents;
|
||||
});
|
||||
|
||||
describe('actions', () => {
|
||||
|
|
|
@ -31,30 +31,31 @@ describe('settings', function (this: Mocha.Suite) {
|
|||
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 newPagePromise = window.context().waitForEvent('page');
|
||||
await window.locator('.NavTabs__ItemIcon--Settings').click();
|
||||
const settingsWindow = await newPagePromise;
|
||||
await settingsWindow.getByText('Device Name').waitFor();
|
||||
await window.getByRole('heading', { name: 'Settings' }).waitFor();
|
||||
|
||||
await settingsWindow.getByText('Appearance').click();
|
||||
await settingsWindow.getByText('Language').first().waitFor();
|
||||
await window.getByRole('button', { name: 'General' }).click();
|
||||
await window.getByText('Device Name').waitFor();
|
||||
|
||||
await settingsWindow.getByText('Chats').click();
|
||||
await settingsWindow.getByText('Spell check text').waitFor();
|
||||
await window.getByRole('button', { name: 'Appearance' }).click();
|
||||
await window.getByText('Language').first().waitFor();
|
||||
|
||||
await settingsWindow.getByText('Calls').click();
|
||||
await settingsWindow.getByText('Enable incoming calls').waitFor();
|
||||
await window.getByRole('button', { name: 'Chats' }).click();
|
||||
await window.getByText('Spell check text').waitFor();
|
||||
|
||||
await settingsWindow.getByText('Notifications').click();
|
||||
await settingsWindow.getByText('Notification content').waitFor();
|
||||
await window.getByRole('button', { name: 'Calls' }).click();
|
||||
await window.getByText('Enable incoming calls').waitFor();
|
||||
|
||||
await settingsWindow.getByText('Privacy').click();
|
||||
await settingsWindow.getByText('Read receipts').waitFor();
|
||||
await window.getByRole('button', { name: 'Notifications' }).click();
|
||||
await window.getByText('Notification content').waitFor();
|
||||
|
||||
await settingsWindow.getByText('Data usage').click();
|
||||
await settingsWindow.getByText('Sent media quality').waitFor();
|
||||
await window.getByRole('button', { name: 'Privacy' }).click();
|
||||
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();
|
||||
|
||||
export function getLinkPreviewSetting(): boolean {
|
||||
return window.storage.get('linkPreviews', false);
|
||||
}
|
||||
|
||||
export function isValidLink(maybeUrl: string | undefined): boolean {
|
||||
if (maybeUrl == null) {
|
||||
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;
|
||||
signedKeyUpdateTimePNI: number;
|
||||
storageKey: string;
|
||||
synced_at: number;
|
||||
synced_at: number | undefined;
|
||||
userAgent: string;
|
||||
uuid_id: string;
|
||||
useRingrtcAdm: boolean;
|
||||
|
@ -148,9 +148,9 @@ export type StorageAccessType = {
|
|||
'storage-service-error-records': ReadonlyArray<UnknownRecord>;
|
||||
'storage-service-unknown-records': ReadonlyArray<UnknownRecord>;
|
||||
'storage-service-pending-deletes': ReadonlyArray<ExtendedStorageID>;
|
||||
'preferred-video-input-device': string;
|
||||
'preferred-audio-input-device': AudioDevice;
|
||||
'preferred-audio-output-device': AudioDevice;
|
||||
'preferred-video-input-device': string | undefined;
|
||||
'preferred-audio-input-device': AudioDevice | undefined;
|
||||
'preferred-audio-output-device': AudioDevice | undefined;
|
||||
remoteConfig: RemoteConfigType;
|
||||
serverTimeSkew: number;
|
||||
unidentifiedDeliveryIndicators: boolean;
|
||||
|
|
|
@ -179,3 +179,19 @@ export type StoryMessageRecipientsType = Array<{
|
|||
distributionListIds: Array<StoryDistributionIdString>;
|
||||
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> &
|
||||
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,
|
||||
isStaging,
|
||||
} from '../util/version';
|
||||
import { isPathInside } from '../util/isPathInside';
|
||||
|
||||
import * as packageJson from '../../package.json';
|
||||
import type { SettingsChannel } from '../main/settingsChannel';
|
||||
import { isPathInside } from '../util/isPathInside';
|
||||
|
||||
import {
|
||||
getSignatureFileName,
|
||||
hexToBinary,
|
||||
verifySignature,
|
||||
} from './signature';
|
||||
|
||||
import type { LoggerType } from '../types/Logging';
|
||||
import type { PrepareDownloadResultType as DifferentialDownloadDataType } from './differential';
|
||||
import {
|
||||
download as downloadDifferentialData,
|
||||
getBlockMapFileName,
|
||||
|
@ -60,6 +57,10 @@ import {
|
|||
isTimeToUpdate,
|
||||
} 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;
|
||||
|
||||
type JSONVendorSchema = {
|
||||
|
@ -109,10 +110,10 @@ type DownloadUpdateResultType = Readonly<{
|
|||
}>;
|
||||
|
||||
export type UpdaterOptionsType = Readonly<{
|
||||
settingsChannel: SettingsChannel;
|
||||
logger: LoggerType;
|
||||
getMainWindow: () => BrowserWindow | undefined;
|
||||
canRunSilently: () => boolean;
|
||||
getMainWindow: () => BrowserWindow | undefined;
|
||||
logger: LoggerType;
|
||||
sql: MainSQL;
|
||||
}>;
|
||||
|
||||
enum CheckType {
|
||||
|
@ -134,7 +135,7 @@ export abstract class Updater {
|
|||
|
||||
protected readonly logger: LoggerType;
|
||||
|
||||
readonly #settingsChannel: SettingsChannel;
|
||||
readonly #sql: MainSQL;
|
||||
|
||||
protected readonly getMainWindow: () => BrowserWindow | undefined;
|
||||
|
||||
|
@ -157,15 +158,15 @@ export abstract class Updater {
|
|||
#pollId = getGuid();
|
||||
|
||||
constructor({
|
||||
settingsChannel,
|
||||
logger,
|
||||
getMainWindow,
|
||||
canRunSilently,
|
||||
getMainWindow,
|
||||
logger,
|
||||
sql,
|
||||
}: UpdaterOptionsType) {
|
||||
this.#settingsChannel = settingsChannel;
|
||||
this.logger = logger;
|
||||
this.getMainWindow = getMainWindow;
|
||||
this.#canRunSilently = canRunSilently;
|
||||
this.getMainWindow = getMainWindow;
|
||||
this.logger = logger;
|
||||
this.#sql = sql;
|
||||
|
||||
this.#throttledSendDownloadingUpdate = throttle(
|
||||
(downloadedSize: number, downloadSize: number) => {
|
||||
|
@ -921,9 +922,11 @@ export abstract class Updater {
|
|||
|
||||
async #getAutoDownloadUpdateSetting(): Promise<boolean> {
|
||||
try {
|
||||
return await this.#settingsChannel.getSettingFromMainWindow(
|
||||
'autoDownloadUpdate'
|
||||
const result = await this.#sql.sqlRead(
|
||||
'getItemById',
|
||||
'auto-download-update'
|
||||
);
|
||||
return result?.value ?? true;
|
||||
} catch (error) {
|
||||
this.logger.warn(
|
||||
'getAutoDownloadUpdateSetting: Failed to fetch, returning false',
|
||||
|
|
|
@ -11,11 +11,15 @@ const ringtoneEventQueue = new PQueue({
|
|||
throwOnTimeout: true,
|
||||
});
|
||||
|
||||
function getCallRingtoneNotificationSetting(): boolean {
|
||||
return window.storage.get('call-ringtone-notification', true);
|
||||
}
|
||||
|
||||
class CallingTones {
|
||||
#ringtone?: Sound;
|
||||
|
||||
async handRaised() {
|
||||
const canPlayTone = window.Events.getCallRingtoneNotification();
|
||||
const canPlayTone = getCallRingtoneNotificationSetting();
|
||||
if (!canPlayTone) {
|
||||
return;
|
||||
}
|
||||
|
@ -28,7 +32,7 @@ class CallingTones {
|
|||
}
|
||||
|
||||
async playEndCall(): Promise<void> {
|
||||
const canPlayTone = window.Events.getCallRingtoneNotification();
|
||||
const canPlayTone = getCallRingtoneNotificationSetting();
|
||||
if (!canPlayTone) {
|
||||
return;
|
||||
}
|
||||
|
@ -46,7 +50,7 @@ class CallingTones {
|
|||
this.#ringtone = undefined;
|
||||
}
|
||||
|
||||
const canPlayTone = window.Events.getCallRingtoneNotification();
|
||||
const canPlayTone = getCallRingtoneNotificationSetting();
|
||||
if (!canPlayTone) {
|
||||
return;
|
||||
}
|
||||
|
@ -70,7 +74,7 @@ class CallingTones {
|
|||
}
|
||||
|
||||
async someonePresenting() {
|
||||
const canPlayTone = window.Events.getCallRingtoneNotification();
|
||||
const canPlayTone = getCallRingtoneNotificationSetting();
|
||||
if (!canPlayTone) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -3,46 +3,20 @@
|
|||
|
||||
import { ipcRenderer } from 'electron';
|
||||
import type { SystemPreferences } from 'electron';
|
||||
import type { AudioDevice } from '@signalapp/ringrtc';
|
||||
import { noop } from 'lodash';
|
||||
|
||||
import type {
|
||||
AutoDownloadAttachmentType,
|
||||
ZoomFactorType,
|
||||
} from '../types/Storage.d';
|
||||
import type {
|
||||
ConversationColorType,
|
||||
CustomColorType,
|
||||
DefaultConversationColorType,
|
||||
} from '../types/Colors';
|
||||
import { DEFAULT_CONVERSATION_COLOR } from '../types/Colors';
|
||||
import type { ZoomFactorType } from '../types/Storage.d';
|
||||
import * as Errors from '../types/errors';
|
||||
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 { writeProfile } from '../services/writeProfile';
|
||||
import {
|
||||
backupsService,
|
||||
type ValidationResultType as BackupValidationResultType,
|
||||
} from '../services/backups';
|
||||
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 { PhoneNumberDiscoverability } from './phoneNumberDiscoverability';
|
||||
import { PhoneNumberSharingMode } from './phoneNumberSharingMode';
|
||||
import { strictAssert, assertDev } from './assert';
|
||||
import * as durations from './durations';
|
||||
import type { DurationInSeconds } from './durations';
|
||||
import { strictAssert } from './assert';
|
||||
import * as Registration from './registration';
|
||||
import { lookupConversationWithoutServiceId } from './lookupConversationWithoutServiceId';
|
||||
import * as log from '../logging/log';
|
||||
import { deleteAllMyStories } from './deleteAllMyStories';
|
||||
import {
|
||||
type NotificationClickData,
|
||||
notificationService,
|
||||
|
@ -50,103 +24,34 @@ import {
|
|||
import { StoryViewModeType, StoryViewTargetType } from '../types/Stories';
|
||||
import { isValidE164 } from './isValidE164';
|
||||
import { fromWebSafeBase64 } from './webSafeBase64';
|
||||
import { getConversation } from './getConversation';
|
||||
import { instance, PhoneNumberFormat } from './libphonenumberInstance';
|
||||
import { showConfirmationDialog } from './showConfirmationDialog';
|
||||
import type {
|
||||
EphemeralSettings,
|
||||
SettingsValuesType,
|
||||
ThemeType,
|
||||
} from './preload';
|
||||
import type { SystemTraySetting } from '../types/SystemTraySetting';
|
||||
import { drop } from './drop';
|
||||
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';
|
||||
import { SystemTraySetting } from '../types/SystemTraySetting';
|
||||
import OS from './os/osPreload';
|
||||
|
||||
export type IPCEventsValuesType = {
|
||||
alwaysRelayCalls: boolean | undefined;
|
||||
audioNotification: boolean | undefined;
|
||||
audioMessage: boolean;
|
||||
autoConvertEmoji: boolean;
|
||||
autoDownloadAttachment: AutoDownloadAttachmentType;
|
||||
autoDownloadUpdate: boolean;
|
||||
// IPC-mediated
|
||||
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;
|
||||
mediaCameraPermissions: boolean | undefined;
|
||||
|
||||
// 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;
|
||||
zoomFactor: ZoomFactorType;
|
||||
};
|
||||
|
||||
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;
|
||||
removeDarkOverlay: () => void;
|
||||
|
||||
cleanupDownloads: () => Promise<void>;
|
||||
deleteAllData: () => Promise<void>;
|
||||
deleteAllMyStories: () => Promise<void>;
|
||||
editCustomColor: (colorId: string, customColor: CustomColorType) => void;
|
||||
getConversationsWithCustomColor: (x: string) => Array<ConversationType>;
|
||||
getIsInCall: () => boolean;
|
||||
getMediaAccessStatus: (
|
||||
mediaType: 'screen' | 'microphone' | 'camera'
|
||||
) => Promise<ReturnType<SystemPreferences['getMediaAccessStatus']>>;
|
||||
installStickerPack: (packId: string, key: string) => Promise<void>;
|
||||
isPrimary: () => boolean;
|
||||
isInternalUser: () => boolean;
|
||||
removeCustomColor: (x: string) => void;
|
||||
removeCustomColorOnConversations: (x: string) => void;
|
||||
removeDarkOverlay: () => void;
|
||||
resetAllChatColors: () => void;
|
||||
resetDefaultChatColor: () => void;
|
||||
requestCloseConfirmation: () => Promise<boolean>;
|
||||
setMediaPlaybackDisabled: (playbackDisabled: boolean) => void;
|
||||
showConversationViaNotification: (data: NotificationClickData) => void;
|
||||
showConversationViaToken: (token: string) => void;
|
||||
|
@ -158,23 +63,9 @@ export type IPCEventsCallbacksType = {
|
|||
showGroupViaLink: (value: string) => Promise<void>;
|
||||
showReleaseNotes: () => void;
|
||||
showStickerPack: (packId: string, key: string) => void;
|
||||
startCallingLobbyViaToken: (token: string) => void;
|
||||
requestCloseConfirmation: () => Promise<boolean>;
|
||||
getIsInCall: () => boolean;
|
||||
shutdown: () => Promise<void>;
|
||||
startCallingLobbyViaToken: (token: string) => 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: (
|
||||
manifest: Uint8Array,
|
||||
stickers: ReadonlyArray<Uint8Array>
|
||||
|
@ -183,42 +74,20 @@ export type IPCEventsCallbacksType = {
|
|||
|
||||
type ValuesWithGetters = Omit<
|
||||
SettingsValuesType,
|
||||
// Async
|
||||
| 'zoomFactor'
|
||||
// Async - we'll redefine these in IPCEventsGettersType
|
||||
| 'autoLaunch'
|
||||
| 'localeOverride'
|
||||
| 'spellCheck'
|
||||
| 'themeSetting'
|
||||
// Optional
|
||||
| 'mediaPermissions'
|
||||
| 'mediaCameraPermissions'
|
||||
| 'autoLaunch'
|
||||
| 'spellCheck'
|
||||
| 'contentProtection'
|
||||
| 'systemTraySetting'
|
||||
| 'themeSetting'
|
||||
| 'zoomFactor'
|
||||
>;
|
||||
|
||||
type ValuesWithSetters = Omit<
|
||||
SettingsValuesType,
|
||||
| 'blockedCount'
|
||||
| 'defaultConversationColor'
|
||||
| 'linkPreviewSetting'
|
||||
| 'readReceiptSetting'
|
||||
| 'typingIndicatorSetting'
|
||||
| 'deviceName'
|
||||
| 'phoneNumber'
|
||||
| 'backupFeatureEnabled'
|
||||
| 'cloudBackupStatus'
|
||||
| 'backupSubscriptionStatus'
|
||||
|
||||
// Optional
|
||||
| 'mediaPermissions'
|
||||
| 'mediaCameraPermissions'
|
||||
|
||||
// Only set in the Settings window
|
||||
| 'localeOverride'
|
||||
| 'spellCheck'
|
||||
| 'systemTraySetting'
|
||||
>;
|
||||
// Right now everything is symmetrical
|
||||
type ValuesWithSetters = SettingsValuesType;
|
||||
|
||||
export type IPCEventsUpdatersType = {
|
||||
[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> =
|
||||
`update${Capitalize<Key>}`;
|
||||
|
||||
export type ZoomFactorChangeCallback = (zoomFactor: ZoomFactorType) => void;
|
||||
export type IPCEventsGettersType = {
|
||||
[Key in keyof ValuesWithGetters as IPCEventGetterType<Key>]: () => ValuesWithGetters[Key];
|
||||
} & {
|
||||
// Async
|
||||
getZoomFactor: () => Promise<ZoomFactorType>;
|
||||
getAutoLaunch: () => Promise<boolean>;
|
||||
getLocaleOverride: () => Promise<string | null>;
|
||||
getMediaPermissions: () => Promise<boolean>;
|
||||
getMediaCameraPermissions: () => Promise<boolean>;
|
||||
getSpellCheck: () => Promise<boolean>;
|
||||
getContentProtection: () => Promise<boolean>;
|
||||
getSystemTraySetting: () => Promise<SystemTraySetting>;
|
||||
getThemeSetting: () => Promise<ThemeType>;
|
||||
getZoomFactor: () => Promise<ZoomFactorType>;
|
||||
// Events
|
||||
onZoomFactorChange: (callback: (zoomFactor: ZoomFactorType) => void) => void;
|
||||
// Optional
|
||||
getMediaPermissions?: () => Promise<boolean>;
|
||||
getMediaCameraPermissions?: () => Promise<boolean>;
|
||||
getAutoLaunch?: () => Promise<boolean>;
|
||||
onZoomFactorChange: (callback: ZoomFactorChangeCallback) => void;
|
||||
offZoomFactorChange: (callback: ZoomFactorChangeCallback) => void;
|
||||
};
|
||||
|
||||
export type IPCEventsSettersType = {
|
||||
|
@ -258,6 +128,7 @@ export type IPCEventsSettersType = {
|
|||
value: NonNullable<ValuesWithSetters[Key]>
|
||||
) => Promise<void>;
|
||||
} & {
|
||||
setLocaleOverride: (value: string | null) => Promise<void>;
|
||||
setMediaPermissions?: (value: boolean) => Promise<void>;
|
||||
setMediaCameraPermissions?: (value: boolean) => Promise<void>;
|
||||
};
|
||||
|
@ -270,325 +141,92 @@ export type IPCEventsType = IPCEventsGettersType &
|
|||
export function createIPCEvents(
|
||||
overrideEvents: Partial<IPCEventsType> = {}
|
||||
): IPCEventsType {
|
||||
const setPhoneNumberDiscoverabilitySetting = async (
|
||||
newValue: PhoneNumberDiscoverability
|
||||
): Promise<void> => {
|
||||
strictAssert(window.textsecure.server, 'WebAPI must be available');
|
||||
await window.storage.put('phoneNumberDiscoverability', newValue);
|
||||
await window.textsecure.server.setPhoneNumberDiscoverability(
|
||||
newValue === PhoneNumberDiscoverability.Discoverable
|
||||
);
|
||||
const account = window.ConversationController.getOurConversationOrThrow();
|
||||
account.captureChange('phoneNumberDiscoverability');
|
||||
};
|
||||
let zoomFactorChangeCallbacks: Array<ZoomFactorChangeCallback> = [];
|
||||
ipcRenderer.on('zoomFactorChanged', (_event, zoomFactor) => {
|
||||
zoomFactorChangeCallbacks.forEach(callback => callback(zoomFactor));
|
||||
});
|
||||
|
||||
return {
|
||||
getDeviceName: () => window.textsecure.storage.user.getDeviceName(),
|
||||
getPhoneNumber: () => {
|
||||
try {
|
||||
const e164 = window.textsecure.storage.user.getNumber();
|
||||
const parsedNumber = instance.parse(e164);
|
||||
return instance.format(parsedNumber, PhoneNumberFormat.INTERNATIONAL);
|
||||
} catch (error) {
|
||||
log.warn(
|
||||
'IPC.getPhoneNumber: failed to parse our E164',
|
||||
Errors.toLogFormat(error)
|
||||
);
|
||||
return '';
|
||||
}
|
||||
// From IPCEventsValuesType
|
||||
getAutoLaunch: async () => {
|
||||
return (await window.IPC.getAutoLaunch()) ?? false;
|
||||
},
|
||||
setAutoLaunch: async (value: boolean) => {
|
||||
await window.IPC.setAutoLaunch(value);
|
||||
},
|
||||
getMediaCameraPermissions: async () => {
|
||||
return (await window.IPC.getMediaCameraPermissions()) ?? false;
|
||||
},
|
||||
setMediaCameraPermissions: async () => {
|
||||
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: () => {
|
||||
return ipcRenderer.invoke('getZoomFactor');
|
||||
},
|
||||
setZoomFactor: async zoomFactor => {
|
||||
ipcRenderer.send('setZoomFactor', zoomFactor);
|
||||
},
|
||||
|
||||
// From IPCEventsGettersType
|
||||
onZoomFactorChange: callback => {
|
||||
ipcRenderer.on('zoomFactorChanged', (_event, zoomFactor) => {
|
||||
callback(zoomFactor);
|
||||
});
|
||||
zoomFactorChangeCallbacks.push(callback);
|
||||
},
|
||||
offZoomFactorChange: toRemove => {
|
||||
zoomFactorChangeCallbacks = zoomFactorChangeCallbacks.filter(
|
||||
callback => toRemove !== callback
|
||||
);
|
||||
},
|
||||
|
||||
setPhoneNumberDiscoverabilitySetting,
|
||||
setPhoneNumberSharingSetting: async (newValue: PhoneNumberSharingMode) => {
|
||||
const account = window.ConversationController.getOurConversationOrThrow();
|
||||
|
||||
const promises = new Array<Promise<void>>();
|
||||
promises.push(window.storage.put('phoneNumberSharingMode', newValue));
|
||||
if (newValue === PhoneNumberSharingMode.Everybody) {
|
||||
promises.push(
|
||||
setPhoneNumberDiscoverabilitySetting(
|
||||
PhoneNumberDiscoverability.Discoverable
|
||||
// From EphemeralSettings
|
||||
getLocaleOverride: async () => {
|
||||
return (await getEphemeralSetting('localeOverride')) ?? null;
|
||||
},
|
||||
setLocaleOverride: async (value: string | null) => {
|
||||
await setEphemeralSetting('localeOverride', value);
|
||||
},
|
||||
getContentProtection: async () => {
|
||||
return (
|
||||
(await getEphemeralSetting('contentProtection')) ??
|
||||
Settings.isContentProtectionEnabledByDefault(
|
||||
OS,
|
||||
window.SignalContext.config.osRelease
|
||||
)
|
||||
);
|
||||
}
|
||||
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,
|
||||
});
|
||||
},
|
||||
|
||||
getHasStoriesDisabled: () =>
|
||||
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: () => {
|
||||
return getEphemeralSetting('contentProtection');
|
||||
},
|
||||
setContentProtection: async (value: boolean) => {
|
||||
await setEphemeralSetting('contentProtection', value);
|
||||
},
|
||||
getStoryViewReceiptsEnabled: () => {
|
||||
getSpellCheck: async () => {
|
||||
return (await getEphemeralSetting('spellCheck')) ?? false;
|
||||
},
|
||||
setSpellCheck: async (value: boolean) => {
|
||||
await setEphemeralSetting('spellCheck', value);
|
||||
},
|
||||
getSystemTraySetting: async () => {
|
||||
return (
|
||||
window.storage.get('storyViewReceiptsEnabled') ??
|
||||
window.storage.get('read-receipt-setting') ??
|
||||
false
|
||||
(await getEphemeralSetting('systemTraySetting')) ??
|
||||
SystemTraySetting.Uninitialized
|
||||
);
|
||||
},
|
||||
setStoryViewReceiptsEnabled: async (value: boolean) => {
|
||||
await window.storage.put('storyViewReceiptsEnabled', value);
|
||||
const account = window.ConversationController.getOurConversationOrThrow();
|
||||
account.captureChange('storyViewReceiptsEnabled');
|
||||
setSystemTraySetting: async (value: SystemTraySetting) => {
|
||||
await setEphemeralSetting('systemTraySetting', value);
|
||||
},
|
||||
|
||||
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 () => {
|
||||
return getEphemeralSetting('themeSetting') ?? null;
|
||||
return (await getEphemeralSetting('themeSetting')) ?? 'system';
|
||||
},
|
||||
setThemeSetting: async value => {
|
||||
drop(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();
|
||||
}
|
||||
setThemeSetting: async (value: ThemeType) => {
|
||||
await setEphemeralSetting('themeSetting', value);
|
||||
},
|
||||
|
||||
// From IPCEventsCallbacksType
|
||||
addDarkOverlay: () => {
|
||||
const elems = document.querySelectorAll('.dark-overlay');
|
||||
if (elems.length) {
|
||||
|
@ -608,45 +246,57 @@ export function createIPCEvents(
|
|||
elem.remove();
|
||||
}
|
||||
},
|
||||
showKeyboardShortcuts: () =>
|
||||
window.reduxActions.globalModals.showShortcutGuideModal(),
|
||||
|
||||
cleanupDownloads: async () => {
|
||||
await ipcRenderer.invoke('cleanup-downloads');
|
||||
},
|
||||
|
||||
deleteAllData: async () => {
|
||||
renderClearingDataView();
|
||||
getIsInCall: (): boolean => {
|
||||
return isInCall(window.reduxStore.getState());
|
||||
},
|
||||
|
||||
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);
|
||||
getMediaAccessStatus: async (
|
||||
mediaType: 'screen' | 'microphone' | 'camera'
|
||||
) => {
|
||||
return window.IPC.getMediaAccessStatus(mediaType);
|
||||
},
|
||||
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'),
|
||||
installStickerPack: async (packId, key) => {
|
||||
void Stickers.downloadStickerPack(packId, key, {
|
||||
finalStatus: 'installed',
|
||||
actionSource: 'ui',
|
||||
});
|
||||
},
|
||||
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;
|
||||
}
|
||||
},
|
||||
setMediaPlaybackDisabled: (playbackDisabled: boolean) => {
|
||||
window.reduxActions?.lightbox.setPlaybackDisabled(playbackDisabled);
|
||||
if (playbackDisabled) {
|
||||
window.reduxActions?.audioPlayer.pauseVoiceNotePlayer();
|
||||
}
|
||||
},
|
||||
|
||||
showConversationViaNotification({
|
||||
conversationId,
|
||||
messageId,
|
||||
|
@ -667,7 +317,6 @@ export function createIPCEvents(
|
|||
});
|
||||
}
|
||||
},
|
||||
|
||||
showConversationViaToken(token: string) {
|
||||
const data = notificationService.resolveToken(token);
|
||||
if (!data) {
|
||||
|
@ -676,7 +325,6 @@ export function createIPCEvents(
|
|||
window.Events.showConversationViaNotification(data);
|
||||
}
|
||||
},
|
||||
|
||||
async showConversationViaSignalDotMe(kind: string, value: string) {
|
||||
if (!Registration.everDone()) {
|
||||
log.info(
|
||||
|
@ -722,7 +370,40 @@ export function createIPCEvents(
|
|||
log.info('showConversationViaSignalDotMe: invalid E164');
|
||||
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) {
|
||||
const data = notificationService.resolveToken(token);
|
||||
if (!data) {
|
||||
|
@ -733,76 +414,10 @@ export function createIPCEvents(
|
|||
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: () => {
|
||||
log.warn('unknownSignalLink: Showing error dialog');
|
||||
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: (
|
||||
manifest: Uint8Array,
|
||||
stickers: ReadonlyArray<Uint8Array>
|
||||
|
@ -812,7 +427,6 @@ export function createIPCEvents(
|
|||
ipcRenderer.send('art-creator:onUploadProgress')
|
||||
);
|
||||
},
|
||||
|
||||
...overrideEvents,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -11,3 +11,12 @@ export function isBackupFeatureEnabled(): boolean {
|
|||
}
|
||||
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);
|
||||
window.Whisper.events.trigger('deviceNameChanged');
|
||||
log.info(
|
||||
'fetchAndUpdateDeviceName: successfully updated new device name locally'
|
||||
);
|
||||
|
|
|
@ -7,8 +7,8 @@ import { strictAssert } from './assert';
|
|||
import * as Errors from '../types/errors';
|
||||
import type { UnwrapPromise } from '../types/Util';
|
||||
import type {
|
||||
IPCEventsValuesType,
|
||||
IPCEventsCallbacksType,
|
||||
IPCEventsValuesType,
|
||||
} from './createIPCEvents';
|
||||
import type { SystemTraySetting } from '../types/SystemTraySetting';
|
||||
|
||||
|
@ -25,11 +25,11 @@ export type SettingType<Value> = Readonly<{
|
|||
export type ThemeType = 'light' | 'dark' | 'system';
|
||||
|
||||
export type EphemeralSettings = {
|
||||
localeOverride: string | null;
|
||||
spellCheck: boolean;
|
||||
contentProtection: boolean;
|
||||
systemTraySetting: SystemTraySetting;
|
||||
themeSetting: ThemeType;
|
||||
localeOverride: string | null;
|
||||
};
|
||||
|
||||
export type SettingsValuesType = IPCEventsValuesType & EphemeralSettings;
|
||||
|
|
|
@ -9,7 +9,7 @@ export async function requestMicrophonePermissions(
|
|||
await window.IPC.showPermissionsPopup(forCalling, false);
|
||||
|
||||
// Check the setting again (from the source of truth).
|
||||
return window.IPC.getMediaPermissions();
|
||||
return window.Events.getMediaPermissions();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
@ -2,6 +2,13 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
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();
|
||||
|
|
|
@ -2,12 +2,16 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { DurationInSeconds } from './durations';
|
||||
import type { ItemsStateType } from '../state/ducks/items';
|
||||
|
||||
export const ITEM_NAME = 'universalExpireTimer';
|
||||
|
||||
export function get(): DurationInSeconds {
|
||||
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> {
|
||||
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>;
|
||||
};
|
||||
drawAttention: () => void;
|
||||
getAutoLaunch: () => Promise<boolean>;
|
||||
getAutoLaunch: () => Promise<boolean | undefined>;
|
||||
getMediaAccessStatus: (
|
||||
mediaType: 'screen' | 'microphone' | 'camera'
|
||||
) => Promise<ReturnType<SystemPreferences['getMediaAccessStatus']>>;
|
||||
|
@ -74,7 +74,7 @@ export type IPCType = {
|
|||
openSystemMediaPermissions: (
|
||||
mediaType: 'microphone' | 'camera' | 'screenCapture'
|
||||
) => Promise<void>;
|
||||
getMediaPermissions: () => Promise<boolean>;
|
||||
getMediaPermissions: () => Promise<boolean | undefined>;
|
||||
whenWindowVisible: () => Promise<void>;
|
||||
logAppLoadedEvent?: (options: { processedCount?: number }) => void;
|
||||
readyForUpdates: () => void;
|
||||
|
@ -82,6 +82,8 @@ export type IPCType = {
|
|||
setAutoHideMenuBar: (value: boolean) => void;
|
||||
setAutoLaunch: (value: boolean) => Promise<void>;
|
||||
setBadge: (badge: number | 'marked-unread') => void;
|
||||
setMediaPermissions: (value: boolean) => Promise<void>;
|
||||
setMediaCameraPermissions: (value: boolean) => Promise<void>;
|
||||
setMenuBarVisibility: (value: boolean) => void;
|
||||
showDebugLog: () => void;
|
||||
showPermissionsPopup: (
|
||||
|
|
|
@ -123,6 +123,10 @@ const IPC: IPCType = {
|
|||
},
|
||||
showPermissionsPopup: (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'),
|
||||
showWindow: () => {
|
||||
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', () => {
|
||||
window.Whisper.events.trigger('setupAsNewDevice');
|
||||
});
|
||||
|
@ -329,19 +337,6 @@ ipc.on('remove-dark-overlay', () => {
|
|||
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) => {
|
||||
window.Events.showStickerPack?.(info.packId, info.packKey);
|
||||
});
|
||||
|
|
|
@ -1,92 +1,10 @@
|
|||
// Copyright 2017 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import {
|
||||
installCallback,
|
||||
installSetting,
|
||||
installEphemeralSetting,
|
||||
} from '../util/preload';
|
||||
import { installEphemeralSetting } from '../util/preload';
|
||||
|
||||
// ChatColorPicker redux hookups
|
||||
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('contentProtection');
|
||||
installEphemeralSetting('localeOverride');
|
||||
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