Ask for confirmation when closing app during call

This commit is contained in:
ayumi-signal 2024-02-19 06:44:05 -08:00 committed by GitHub
parent c772f2abc5
commit 93c019dc30
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 129 additions and 0 deletions

View file

@ -5723,6 +5723,14 @@
"messageformat": "Would you like to discard these changes?",
"description": "ConfirmationDialog text for discarding changes"
},
"icu:ConfirmationDialog__Title--in-call-close-requested": {
"messageformat": "Close Signal and end the call?",
"description": "ConfirmationDialog title when trying to close the app while in a call."
},
"icu:ConfirmationDialog__Title--close-requested-not-now": {
"messageformat": "Not Now",
"description": "Confirmation dialog button to cancel closing the app, while in a call and trying to close the app."
},
"icu:ProfileEditor--edit-photo": {
"messageformat": "Edit photo",
"description": "Text of a button on profile editor that leads to the avatar editor"

View file

@ -862,6 +862,21 @@ async function createWindow() {
// Prevent the shutdown
e.preventDefault();
// In certain cases such as during an active call, we ask the user to confirm close
// which includes shutdown, clicking X on MacOS or closing to tray.
let shouldClose = true;
try {
shouldClose = await maybeRequestCloseConfirmation();
} catch (error) {
getLogger().warn(
'Error while requesting close confirmation.',
Errors.toLogFormat(error)
);
}
if (!shouldClose) {
return;
}
/**
* if the user is in fullscreen mode and closes the window, not the
* application, we need them leave fullscreen first before closing it to
@ -2057,6 +2072,59 @@ function setupMenu(options?: Partial<CreateTemplateOptionsType>) {
});
}
async function maybeRequestCloseConfirmation(): Promise<boolean> {
if (!mainWindow || !mainWindow.webContents) {
return true;
}
getLogger().info(
'maybeRequestCloseConfirmation: Checking to see if close confirmation is needed'
);
const request = new Promise<boolean>(resolveFn => {
let timeout: NodeJS.Timeout | undefined;
if (!mainWindow) {
resolveFn(true);
return;
}
ipc.once('received-close-confirmation', (_event, result) => {
getLogger().info('maybeRequestCloseConfirmation: Response received');
clearTimeoutIfNecessary(timeout);
resolveFn(result);
});
ipc.once('requested-close-confirmation', () => {
getLogger().info(
'maybeRequestCloseConfirmation: Confirmation dialog shown, waiting for user.'
);
clearTimeoutIfNecessary(timeout);
});
mainWindow.webContents.send('maybe-request-close-confirmation');
// Wait a short time then proceed. Normally the dialog should be
// shown right away.
timeout = setTimeout(() => {
getLogger().error(
'maybeRequestCloseConfirmation: Response never received; continuing with close.'
);
resolveFn(true);
}, 10 * 1000);
});
try {
return await request;
} catch (error) {
getLogger().error(
'maybeRequestCloseConfirmation error:',
Errors.toLogFormat(error)
);
return true;
}
}
async function requestShutdown() {
if (!mainWindow || !mainWindow.webContents) {
return;

View file

@ -22,6 +22,7 @@ import type { AuthorizeArtCreatorDataType } from '../state/ducks/globalModals';
import { calling } from '../services/calling';
import { resolveUsernameByLinkBase64 } from '../services/username';
import { writeProfile } from '../services/writeProfile';
import { isInCall as getIsInCall } from '../state/selectors/calling';
import { getConversationsWithCustomColorSelector } from '../state/selectors/conversations';
import { getCustomColors } from '../state/selectors/items';
import { themeChanged } from '../shims/themeChanged';
@ -43,6 +44,7 @@ import { isValidE164 } from './isValidE164';
import { fromWebSafeBase64 } from './webSafeBase64';
import { getConversation } from './getConversation';
import { instance, PhoneNumberFormat } from './libphonenumberInstance';
import { showConfirmationDialog } from './showConfirmationDialog';
type SentMediaQualityType = 'standard' | 'high';
type ThemeType = 'light' | 'dark' | 'system';
@ -129,6 +131,7 @@ export type IPCEventsCallbacksType = {
showGroupViaLink: (value: string) => Promise<void>;
showReleaseNotes: () => void;
showStickerPack: (packId: string, key: string) => void;
maybeRequestCloseConfirmation: () => Promise<boolean>;
shutdown: () => Promise<void>;
unknownSignalLink: () => void;
getCustomColors: () => Record<string, CustomColorType>;
@ -620,6 +623,43 @@ export function createIPCEvents(
showUnknownSgnlLinkModal();
},
maybeRequestCloseConfirmation: async (): Promise<boolean> => {
const isInCall = getIsInCall(window.reduxStore.getState());
if (!isInCall) {
return true;
}
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('Close confirmed by user.');
if (isInCall) {
window.reduxActions.calling.hangUpActiveCall(
'User confirmed in-call close.'
);
}
return true;
} catch {
log.info('Close cancelled by user.');
return false;
}
},
unknownSignalLink: () => {
log.warn('unknownSignalLink: Showing error dialog');
showUnknownSgnlLinkModal();

View file

@ -388,6 +388,19 @@ ipc.on('get-ready-for-shutdown', async () => {
}
});
ipc.on('maybe-request-close-confirmation', async () => {
const { maybeRequestCloseConfirmation } = window.Events;
if (!maybeRequestCloseConfirmation) {
ipc.send('received-close-confirmation', true);
return;
}
log.info('Requesting close confirmation.');
ipc.send('requested-close-confirmation');
const result = await maybeRequestCloseConfirmation();
ipc.send('received-close-confirmation', result);
});
ipc.on('show-release-notes', () => {
const { showReleaseNotes } = window.Events;
if (showReleaseNotes) {