Show notification on screenshare start in adhoc call
This commit is contained in:
parent
22192a4037
commit
1a4bc49563
7 changed files with 114 additions and 32 deletions
|
@ -3,7 +3,11 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { Meta } from '@storybook/react';
|
import type { Meta } from '@storybook/react';
|
||||||
import { IdenticonSVGForContact, IdenticonSVGForGroup } from './IdenticonSVG';
|
import {
|
||||||
|
IdenticonSVGForCallLink,
|
||||||
|
IdenticonSVGForContact,
|
||||||
|
IdenticonSVGForGroup,
|
||||||
|
} from './IdenticonSVG';
|
||||||
import { AvatarColorMap } from '../types/Colors';
|
import { AvatarColorMap } from '../types/Colors';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -40,3 +44,18 @@ export function AllColorsForGroup(): JSX.Element {
|
||||||
|
|
||||||
return <>{stories}</>;
|
return <>{stories}</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function AllColorsForCallLink(): JSX.Element {
|
||||||
|
const stories: Array<JSX.Element> = [];
|
||||||
|
|
||||||
|
AvatarColorMap.forEach(value =>
|
||||||
|
stories.push(
|
||||||
|
<IdenticonSVGForCallLink
|
||||||
|
backgroundColor={value.bg}
|
||||||
|
foregroundColor={value.fg}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return <>{stories}</>;
|
||||||
|
}
|
||||||
|
|
|
@ -67,3 +67,25 @@ export function IdenticonSVGForGroup({
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function IdenticonSVGForCallLink({
|
||||||
|
backgroundColor,
|
||||||
|
foregroundColor,
|
||||||
|
}: PropsTypeForGroup): JSX.Element {
|
||||||
|
// Note: the inner SVG below is taken from images/icons/v3/video/video-display-bold.svg,
|
||||||
|
// viewBox added to match the original SVG, new dimensions to create match Avatar.tsx.
|
||||||
|
return (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
|
||||||
|
<circle cx="50" cy="50" r="40" fill={backgroundColor} />
|
||||||
|
<svg viewBox="0 0 36 36" height="50" width="50" y="26" x="25">
|
||||||
|
<path
|
||||||
|
fill={foregroundColor}
|
||||||
|
fillRule="evenodd"
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
d="M10.302 5.625c-1.22 0-2.203 0-3 .065-.82.067-1.54.209-2.206.548a5.625 5.625 0 0 0-2.458 2.458c-.34.667-.481 1.387-.548 2.207-.065.796-.065 1.78-.065 2.999v8.196c0 1.22 0 2.203.065 3 .067.82.208 1.54.548 2.206a5.625 5.625 0 0 0 2.458 2.458c.667.34 1.387.481 2.207.548.796.065 1.78.065 2.999.065h7.296c1.22 0 2.203 0 3-.065.82-.067 1.54-.209 2.206-.548a5.625 5.625 0 0 0 2.458-2.458c.34-.667.48-1.387.548-2.207.065-.796.065-1.78.065-2.999v-.032l4.775 4.775c1.559 1.56 4.225.455 4.225-1.75V10.909c0-2.205-2.666-3.31-4.225-1.75l-4.775 4.775v-.032c0-1.22 0-2.203-.065-3-.067-.82-.209-1.54-.548-2.206a5.625 5.625 0 0 0-2.458-2.458c-.667-.34-1.387-.481-2.207-.548-.796-.065-1.78-.065-2.999-.065h-7.296Zm13.323 8.325c0-1.279-.001-2.17-.058-2.864-.055-.68-.159-1.072-.31-1.368a3.374 3.374 0 0 0-1.475-1.475c-.296-.151-.687-.255-1.368-.31-.694-.057-1.585-.058-2.864-.058h-7.2c-1.279 0-2.17 0-2.864.058-.68.055-1.072.159-1.368.31a3.375 3.375 0 0 0-1.475 1.475c-.151.296-.255.687-.31 1.368-.057.694-.058 1.585-.058 2.864v8.1c0 1.279 0 2.17.057 2.864.056.68.16 1.072.31 1.368.324.635.84 1.152 1.476 1.475.296.151.687.255 1.368.31.694.057 1.585.058 2.864.058h7.2c1.279 0 2.17 0 2.864-.058.68-.055 1.072-.159 1.368-.31a3.374 3.374 0 0 0 1.475-1.475c.151-.296.255-.687.31-1.368.057-.694.058-1.585.058-2.864v-8.1Zm2.25 4.05c0 .566.225 1.109.625 1.51l5.74 5.74a.21.21 0 0 0 .116.066.24.24 0 0 0 .13-.017.239.239 0 0 0 .104-.08.21.21 0 0 0 .035-.128V10.909a.21.21 0 0 0-.035-.128.238.238 0 0 0-.104-.08.24.24 0 0 0-.13-.017.21.21 0 0 0-.115.066L26.5 16.49c-.4.401-.625.944-.625 1.51Z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -37,7 +37,6 @@ import {
|
||||||
} from '../util/avatarUtils';
|
} from '../util/avatarUtils';
|
||||||
import { getDraftPreview } from '../util/getDraftPreview';
|
import { getDraftPreview } from '../util/getDraftPreview';
|
||||||
import { hasDraft } from '../util/hasDraft';
|
import { hasDraft } from '../util/hasDraft';
|
||||||
import { missingCaseError } from '../util/missingCaseError';
|
|
||||||
import { hydrateStoryContext } from '../util/hydrateStoryContext';
|
import { hydrateStoryContext } from '../util/hydrateStoryContext';
|
||||||
import * as Conversation from '../types/Conversation';
|
import * as Conversation from '../types/Conversation';
|
||||||
import type { StickerType, StickerWithHydratedData } from '../types/Stickers';
|
import type { StickerType, StickerWithHydratedData } from '../types/Stickers';
|
||||||
|
@ -94,8 +93,8 @@ import { migrateColor } from '../util/migrateColor';
|
||||||
import { isNotNil } from '../util/isNotNil';
|
import { isNotNil } from '../util/isNotNil';
|
||||||
import {
|
import {
|
||||||
NotificationType,
|
NotificationType,
|
||||||
NotificationSetting,
|
|
||||||
notificationService,
|
notificationService,
|
||||||
|
shouldSaveNotificationAvatarToDisk,
|
||||||
} from '../services/notifications';
|
} from '../services/notifications';
|
||||||
import { storageServiceUploadJob } from '../services/storage';
|
import { storageServiceUploadJob } from '../services/storage';
|
||||||
import { getSendOptions } from '../util/getSendOptions';
|
import { getSendOptions } from '../util/getSendOptions';
|
||||||
|
@ -172,7 +171,6 @@ import { ReceiptType } from '../types/Receipt';
|
||||||
import { getQuoteAttachment } from '../util/makeQuote';
|
import { getQuoteAttachment } from '../util/makeQuote';
|
||||||
import { deriveProfileKeyVersion } from '../util/zkgroup';
|
import { deriveProfileKeyVersion } from '../util/zkgroup';
|
||||||
import { incrementMessageCounter } from '../util/incrementMessageCounter';
|
import { incrementMessageCounter } from '../util/incrementMessageCounter';
|
||||||
import OS from '../util/os/osMain';
|
|
||||||
import { getMessageAuthorText } from '../util/getMessageAuthorText';
|
import { getMessageAuthorText } from '../util/getMessageAuthorText';
|
||||||
import { downscaleOutgoingAttachment } from '../util/attachments';
|
import { downscaleOutgoingAttachment } from '../util/attachments';
|
||||||
import { MessageRequestResponseEvent } from '../types/MessageRequestResponseEvent';
|
import { MessageRequestResponseEvent } from '../types/MessageRequestResponseEvent';
|
||||||
|
@ -5292,24 +5290,7 @@ export class ConversationModel extends window.Backbone
|
||||||
url: string;
|
url: string;
|
||||||
absolutePath?: string;
|
absolutePath?: string;
|
||||||
}> {
|
}> {
|
||||||
let saveToDisk: boolean;
|
const saveToDisk = shouldSaveNotificationAvatarToDisk();
|
||||||
|
|
||||||
const notificationSetting = notificationService.getNotificationSetting();
|
|
||||||
switch (notificationSetting) {
|
|
||||||
case NotificationSetting.NameOnly:
|
|
||||||
case NotificationSetting.NameAndMessage:
|
|
||||||
// According to the MSDN, avatars can only be loaded from disk or an
|
|
||||||
// http server:
|
|
||||||
// https://learn.microsoft.com/en-us/uwp/schemas/tiles/toastschema/element-image?redirectedfrom=MSDN
|
|
||||||
saveToDisk = OS.isWindows();
|
|
||||||
break;
|
|
||||||
case NotificationSetting.Off:
|
|
||||||
case NotificationSetting.NoNameOrMessage:
|
|
||||||
saveToDisk = false;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw missingCaseError(notificationSetting);
|
|
||||||
}
|
|
||||||
const avatarUrl = getLocalAvatarUrl(this.attributes);
|
const avatarUrl = getLocalAvatarUrl(this.attributes);
|
||||||
if (avatarUrl) {
|
if (avatarUrl) {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -110,6 +110,7 @@ import {
|
||||||
NotificationSetting,
|
NotificationSetting,
|
||||||
FALLBACK_NOTIFICATION_TITLE,
|
FALLBACK_NOTIFICATION_TITLE,
|
||||||
NotificationType,
|
NotificationType,
|
||||||
|
shouldSaveNotificationAvatarToDisk,
|
||||||
} from './notifications';
|
} from './notifications';
|
||||||
import * as log from '../logging/log';
|
import * as log from '../logging/log';
|
||||||
import { assertDev, strictAssert } from '../util/assert';
|
import { assertDev, strictAssert } from '../util/assert';
|
||||||
|
@ -154,6 +155,8 @@ import type { CallLinkType, CallLinkStateType } from '../types/CallLink';
|
||||||
import { CallLinkRestrictions } from '../types/CallLink';
|
import { CallLinkRestrictions } from '../types/CallLink';
|
||||||
import { getConversationIdForLogging } from '../util/idForLogging';
|
import { getConversationIdForLogging } from '../util/idForLogging';
|
||||||
import { sendCallLinkUpdateSync } from '../util/sendCallLinkUpdateSync';
|
import { sendCallLinkUpdateSync } from '../util/sendCallLinkUpdateSync';
|
||||||
|
import { createIdenticon } from '../util/createIdenticon';
|
||||||
|
import { getColorForCallLink } from '../util/getColorForCallLink';
|
||||||
|
|
||||||
const { wasGroupCallRingPreviouslyCanceled } = DataReader;
|
const { wasGroupCallRingPreviouslyCanceled } = DataReader;
|
||||||
const {
|
const {
|
||||||
|
@ -2027,7 +2030,8 @@ export class CallingClass {
|
||||||
async setPresenting(
|
async setPresenting(
|
||||||
conversationId: string,
|
conversationId: string,
|
||||||
hasLocalVideo: boolean,
|
hasLocalVideo: boolean,
|
||||||
source?: PresentedSource
|
source?: PresentedSource,
|
||||||
|
callLinkRootKey?: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const call = getOwn(this.callsLookup, conversationId);
|
const call = getOwn(this.callsLookup, conversationId);
|
||||||
if (!call) {
|
if (!call) {
|
||||||
|
@ -2062,19 +2066,34 @@ export class CallingClass {
|
||||||
if (source) {
|
if (source) {
|
||||||
ipcRenderer.send('show-screen-share', source.name);
|
ipcRenderer.send('show-screen-share', source.name);
|
||||||
|
|
||||||
// TODO: DESKTOP-7068
|
let url: string;
|
||||||
|
let absolutePath: string | undefined;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
call instanceof GroupCall &&
|
call instanceof GroupCall &&
|
||||||
call.getKind() === GroupCallKind.CallLink
|
call.getKind() === GroupCallKind.CallLink
|
||||||
) {
|
) {
|
||||||
return;
|
strictAssert(callLinkRootKey, 'If call is adhoc, we need rootKey');
|
||||||
|
const color = getColorForCallLink(callLinkRootKey);
|
||||||
|
const saveToDisk = shouldSaveNotificationAvatarToDisk();
|
||||||
|
const result = await createIdenticon(
|
||||||
|
color,
|
||||||
|
{ type: 'call-link' },
|
||||||
|
{ saveToDisk }
|
||||||
|
);
|
||||||
|
url = result.url;
|
||||||
|
absolutePath = result.path
|
||||||
|
? window.Signal.Migrations.getAbsoluteTempPath(result.path)
|
||||||
|
: undefined;
|
||||||
|
} else {
|
||||||
|
const conversation = window.ConversationController.get(conversationId);
|
||||||
|
strictAssert(conversation, 'setPresenting: conversation not found');
|
||||||
|
|
||||||
|
const result = await conversation.getAvatarOrIdenticon();
|
||||||
|
url = result.url;
|
||||||
|
absolutePath = result.absolutePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
const conversation = window.ConversationController.get(conversationId);
|
|
||||||
strictAssert(conversation, 'setPresenting: conversation not found');
|
|
||||||
|
|
||||||
const { url, absolutePath } = await conversation.getAvatarOrIdenticon();
|
|
||||||
|
|
||||||
notificationService.notify({
|
notificationService.notify({
|
||||||
conversationId,
|
conversationId,
|
||||||
iconPath: absolutePath,
|
iconPath: absolutePath,
|
||||||
|
|
|
@ -488,3 +488,20 @@ function filterNotificationText(text: string) {
|
||||||
.replace(/</g, '<')
|
.replace(/</g, '<')
|
||||||
.replace(/>/g, '>');
|
.replace(/>/g, '>');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function shouldSaveNotificationAvatarToDisk(): boolean {
|
||||||
|
const notificationSetting = notificationService.getNotificationSetting();
|
||||||
|
switch (notificationSetting) {
|
||||||
|
case NotificationSetting.NameOnly:
|
||||||
|
case NotificationSetting.NameAndMessage:
|
||||||
|
// According to the MSDN, avatars can only be loaded from disk or an
|
||||||
|
// http server:
|
||||||
|
// https://learn.microsoft.com/en-us/uwp/schemas/tiles/toastschema/element-image?redirectedfrom=MSDN
|
||||||
|
return OS.isWindows();
|
||||||
|
case NotificationSetting.Off:
|
||||||
|
case NotificationSetting.NoNameOrMessage:
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
throw missingCaseError(notificationSetting);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -96,6 +96,7 @@ import { addCallHistory } from './callHistory';
|
||||||
import { saveDraftRecordingIfNeeded } from './composer';
|
import { saveDraftRecordingIfNeeded } from './composer';
|
||||||
import type { CallHistoryDetails } from '../../types/CallDisposition';
|
import type { CallHistoryDetails } from '../../types/CallDisposition';
|
||||||
import type { StartCallData } from '../../components/ConfirmLeaveCallModal';
|
import type { StartCallData } from '../../components/ConfirmLeaveCallModal';
|
||||||
|
import { getCallLinksByRoomId } from '../selectors/calling';
|
||||||
|
|
||||||
// State
|
// State
|
||||||
|
|
||||||
|
@ -1794,7 +1795,9 @@ function setPresenting(
|
||||||
sourceToPresent?: PresentedSource
|
sourceToPresent?: PresentedSource
|
||||||
): ThunkAction<void, RootStateType, unknown, SetPresentingFulfilledActionType> {
|
): ThunkAction<void, RootStateType, unknown, SetPresentingFulfilledActionType> {
|
||||||
return async (dispatch, getState) => {
|
return async (dispatch, getState) => {
|
||||||
const callingState = getState().calling;
|
const state = getState();
|
||||||
|
const callingState = state.calling;
|
||||||
|
|
||||||
const { activeCallState } = callingState;
|
const { activeCallState } = callingState;
|
||||||
const activeCall = getActiveCall(callingState);
|
const activeCall = getActiveCall(callingState);
|
||||||
if (!activeCall || !activeCallState) {
|
if (!activeCall || !activeCallState) {
|
||||||
|
@ -1802,10 +1805,20 @@ function setPresenting(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let rootKey: string | undefined;
|
||||||
|
if (activeCall.callMode === CallMode.Adhoc) {
|
||||||
|
const callLink = getOwn(
|
||||||
|
getCallLinksByRoomId(state),
|
||||||
|
activeCall.conversationId
|
||||||
|
);
|
||||||
|
rootKey = callLink?.rootKey;
|
||||||
|
}
|
||||||
|
|
||||||
await calling.setPresenting(
|
await calling.setPresenting(
|
||||||
activeCall.conversationId,
|
activeCall.conversationId,
|
||||||
activeCallState.hasLocalVideo,
|
activeCallState.hasLocalVideo,
|
||||||
sourceToPresent
|
sourceToPresent,
|
||||||
|
rootKey
|
||||||
);
|
);
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { renderToString } from 'react-dom/server';
|
||||||
import type { AvatarColorType } from '../types/Colors';
|
import type { AvatarColorType } from '../types/Colors';
|
||||||
import { AvatarColorMap } from '../types/Colors';
|
import { AvatarColorMap } from '../types/Colors';
|
||||||
import {
|
import {
|
||||||
|
IdenticonSVGForCallLink,
|
||||||
IdenticonSVGForContact,
|
IdenticonSVGForContact,
|
||||||
IdenticonSVGForGroup,
|
IdenticonSVGForGroup,
|
||||||
} from '../components/IdenticonSVG';
|
} from '../components/IdenticonSVG';
|
||||||
|
@ -21,6 +22,9 @@ type IdenticonDetailsType =
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'group';
|
type: 'group';
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'call-link';
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createIdenticon(
|
export function createIdenticon(
|
||||||
|
@ -47,6 +51,13 @@ export function createIdenticon(
|
||||||
foregroundColor={avatarColor?.fg || defaultColorValue.fg}
|
foregroundColor={avatarColor?.fg || defaultColorValue.fg}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
} else if (details.type === 'call-link') {
|
||||||
|
html = renderToString(
|
||||||
|
<IdenticonSVGForCallLink
|
||||||
|
backgroundColor={avatarColor?.bg || defaultColorValue.bg}
|
||||||
|
foregroundColor={avatarColor?.fg || defaultColorValue.fg}
|
||||||
|
/>
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
throw missingCaseError(details);
|
throw missingCaseError(details);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue