From 1a4bc495634a9043c7d909eb1f865a7476c64038 Mon Sep 17 00:00:00 2001 From: Scott Nonnenberg Date: Fri, 2 Aug 2024 10:01:40 -0700 Subject: [PATCH] Show notification on screenshare start in adhoc call --- ts/components/IdenticonSVG.stories.tsx | 21 +++++++++++++++- ts/components/IdenticonSVG.tsx | 22 ++++++++++++++++ ts/models/conversations.ts | 23 ++--------------- ts/services/calling.ts | 35 ++++++++++++++++++++------ ts/services/notifications.ts | 17 +++++++++++++ ts/state/ducks/calling.ts | 17 +++++++++++-- ts/util/createIdenticon.tsx | 11 ++++++++ 7 files changed, 114 insertions(+), 32 deletions(-) diff --git a/ts/components/IdenticonSVG.stories.tsx b/ts/components/IdenticonSVG.stories.tsx index 0f5f041e0..eb01fdd78 100644 --- a/ts/components/IdenticonSVG.stories.tsx +++ b/ts/components/IdenticonSVG.stories.tsx @@ -3,7 +3,11 @@ import React from 'react'; import type { Meta } from '@storybook/react'; -import { IdenticonSVGForContact, IdenticonSVGForGroup } from './IdenticonSVG'; +import { + IdenticonSVGForCallLink, + IdenticonSVGForContact, + IdenticonSVGForGroup, +} from './IdenticonSVG'; import { AvatarColorMap } from '../types/Colors'; export default { @@ -40,3 +44,18 @@ export function AllColorsForGroup(): JSX.Element { return <>{stories}; } + +export function AllColorsForCallLink(): JSX.Element { + const stories: Array = []; + + AvatarColorMap.forEach(value => + stories.push( + + ) + ); + + return <>{stories}; +} diff --git a/ts/components/IdenticonSVG.tsx b/ts/components/IdenticonSVG.tsx index 63309a864..40e2aec9c 100644 --- a/ts/components/IdenticonSVG.tsx +++ b/ts/components/IdenticonSVG.tsx @@ -67,3 +67,25 @@ export function IdenticonSVGForGroup({ ); } + +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 ( + + + + + + + ); +} diff --git a/ts/models/conversations.ts b/ts/models/conversations.ts index 12a448035..2947ffabd 100644 --- a/ts/models/conversations.ts +++ b/ts/models/conversations.ts @@ -37,7 +37,6 @@ import { } from '../util/avatarUtils'; import { getDraftPreview } from '../util/getDraftPreview'; import { hasDraft } from '../util/hasDraft'; -import { missingCaseError } from '../util/missingCaseError'; import { hydrateStoryContext } from '../util/hydrateStoryContext'; import * as Conversation from '../types/Conversation'; import type { StickerType, StickerWithHydratedData } from '../types/Stickers'; @@ -94,8 +93,8 @@ import { migrateColor } from '../util/migrateColor'; import { isNotNil } from '../util/isNotNil'; import { NotificationType, - NotificationSetting, notificationService, + shouldSaveNotificationAvatarToDisk, } from '../services/notifications'; import { storageServiceUploadJob } from '../services/storage'; import { getSendOptions } from '../util/getSendOptions'; @@ -172,7 +171,6 @@ import { ReceiptType } from '../types/Receipt'; import { getQuoteAttachment } from '../util/makeQuote'; import { deriveProfileKeyVersion } from '../util/zkgroup'; import { incrementMessageCounter } from '../util/incrementMessageCounter'; -import OS from '../util/os/osMain'; import { getMessageAuthorText } from '../util/getMessageAuthorText'; import { downscaleOutgoingAttachment } from '../util/attachments'; import { MessageRequestResponseEvent } from '../types/MessageRequestResponseEvent'; @@ -5292,24 +5290,7 @@ export class ConversationModel extends window.Backbone url: string; absolutePath?: string; }> { - let saveToDisk: 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 - saveToDisk = OS.isWindows(); - break; - case NotificationSetting.Off: - case NotificationSetting.NoNameOrMessage: - saveToDisk = false; - break; - default: - throw missingCaseError(notificationSetting); - } + const saveToDisk = shouldSaveNotificationAvatarToDisk(); const avatarUrl = getLocalAvatarUrl(this.attributes); if (avatarUrl) { return { diff --git a/ts/services/calling.ts b/ts/services/calling.ts index 00385977f..1ff95d940 100644 --- a/ts/services/calling.ts +++ b/ts/services/calling.ts @@ -110,6 +110,7 @@ import { NotificationSetting, FALLBACK_NOTIFICATION_TITLE, NotificationType, + shouldSaveNotificationAvatarToDisk, } from './notifications'; import * as log from '../logging/log'; import { assertDev, strictAssert } from '../util/assert'; @@ -154,6 +155,8 @@ import type { CallLinkType, CallLinkStateType } from '../types/CallLink'; import { CallLinkRestrictions } from '../types/CallLink'; import { getConversationIdForLogging } from '../util/idForLogging'; import { sendCallLinkUpdateSync } from '../util/sendCallLinkUpdateSync'; +import { createIdenticon } from '../util/createIdenticon'; +import { getColorForCallLink } from '../util/getColorForCallLink'; const { wasGroupCallRingPreviouslyCanceled } = DataReader; const { @@ -2027,7 +2030,8 @@ export class CallingClass { async setPresenting( conversationId: string, hasLocalVideo: boolean, - source?: PresentedSource + source?: PresentedSource, + callLinkRootKey?: string ): Promise { const call = getOwn(this.callsLookup, conversationId); if (!call) { @@ -2062,19 +2066,34 @@ export class CallingClass { if (source) { ipcRenderer.send('show-screen-share', source.name); - // TODO: DESKTOP-7068 + let url: string; + let absolutePath: string | undefined; + if ( call instanceof GroupCall && 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({ conversationId, iconPath: absolutePath, diff --git a/ts/services/notifications.ts b/ts/services/notifications.ts index dbbd940d8..a6997eb6c 100644 --- a/ts/services/notifications.ts +++ b/ts/services/notifications.ts @@ -488,3 +488,20 @@ function filterNotificationText(text: string) { .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); + } +} diff --git a/ts/state/ducks/calling.ts b/ts/state/ducks/calling.ts index 616f6552b..88152d17a 100644 --- a/ts/state/ducks/calling.ts +++ b/ts/state/ducks/calling.ts @@ -96,6 +96,7 @@ import { addCallHistory } from './callHistory'; import { saveDraftRecordingIfNeeded } from './composer'; import type { CallHistoryDetails } from '../../types/CallDisposition'; import type { StartCallData } from '../../components/ConfirmLeaveCallModal'; +import { getCallLinksByRoomId } from '../selectors/calling'; // State @@ -1794,7 +1795,9 @@ function setPresenting( sourceToPresent?: PresentedSource ): ThunkAction { return async (dispatch, getState) => { - const callingState = getState().calling; + const state = getState(); + const callingState = state.calling; + const { activeCallState } = callingState; const activeCall = getActiveCall(callingState); if (!activeCall || !activeCallState) { @@ -1802,10 +1805,20 @@ function setPresenting( return; } + let rootKey: string | undefined; + if (activeCall.callMode === CallMode.Adhoc) { + const callLink = getOwn( + getCallLinksByRoomId(state), + activeCall.conversationId + ); + rootKey = callLink?.rootKey; + } + await calling.setPresenting( activeCall.conversationId, activeCallState.hasLocalVideo, - sourceToPresent + sourceToPresent, + rootKey ); dispatch({ diff --git a/ts/util/createIdenticon.tsx b/ts/util/createIdenticon.tsx index 30da8b42d..cc6bc3f9e 100644 --- a/ts/util/createIdenticon.tsx +++ b/ts/util/createIdenticon.tsx @@ -7,6 +7,7 @@ import { renderToString } from 'react-dom/server'; import type { AvatarColorType } from '../types/Colors'; import { AvatarColorMap } from '../types/Colors'; import { + IdenticonSVGForCallLink, IdenticonSVGForContact, IdenticonSVGForGroup, } from '../components/IdenticonSVG'; @@ -21,6 +22,9 @@ type IdenticonDetailsType = } | { type: 'group'; + } + | { + type: 'call-link'; }; export function createIdenticon( @@ -47,6 +51,13 @@ export function createIdenticon( foregroundColor={avatarColor?.fg || defaultColorValue.fg} /> ); + } else if (details.type === 'call-link') { + html = renderToString( + + ); } else { throw missingCaseError(details); }