diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 2bc7b76815..6edaca2677 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1625,6 +1625,10 @@ "messageformat": "Call full", "description": "Button in the call lobby when you can't join because the call is full" }, + "icu:CallingLobbyJoinButton--ask-to-join": { + "messageformat": "Ask to join", + "description": "Button label in the call lobby for joining a call link which requires admin approval" + }, "icu:calling__button--video-disabled": { "messageformat": "Camera disabled", "description": "Button tooltip label when the camera is disabled" diff --git a/ts/components/CallManager.tsx b/ts/components/CallManager.tsx index 0d05df7d31..e73b62c935 100644 --- a/ts/components/CallManager.tsx +++ b/ts/components/CallManager.tsx @@ -53,6 +53,7 @@ import { CallingAdhocCallInfo } from './CallingAdhocCallInfo'; import { callLinkRootKeyToUrl } from '../util/callLinkRootKeyToUrl'; import { ToastType } from '../types/Toast'; import type { ShowToastAction } from '../state/ducks/toast'; +import { isSharingPhoneNumberWithEverybody } from '../util/phoneNumberSharingMode'; const GROUP_CALL_RING_DURATION = 60 * 1000; @@ -257,6 +258,7 @@ function ActiveCallManager({ | undefined | Array>; let isConvoTooBigToRing = false; + let isAdhocAdminApprovalRequired = false; let isAdhocJoinRequestPending = false; switch (activeCall.callMode) { @@ -286,8 +288,11 @@ function ActiveCallManager({ isCallFull = activeCall.deviceCount >= activeCall.maxDevices; isConvoTooBigToRing = activeCall.isConversationTooBigToRing; ({ groupMembers } = activeCall); + isAdhocAdminApprovalRequired = + !callLink?.adminKey && + callLink?.restrictions === CallLinkRestrictions.AdminApproval; isAdhocJoinRequestPending = - callLink?.restrictions === CallLinkRestrictions.AdminApproval && + isAdhocAdminApprovalRequired && activeCall.joinState === GroupCallJoinState.Pending; break; } @@ -324,9 +329,11 @@ function ActiveCallManager({ hasLocalAudio={hasLocalAudio} hasLocalVideo={hasLocalVideo} i18n={i18n} + isAdhocAdminApprovalRequired={isAdhocAdminApprovalRequired} isAdhocJoinRequestPending={isAdhocJoinRequestPending} isCallFull={isCallFull} isConversationTooBigToRing={isConvoTooBigToRing} + isSharingPhoneNumberWithEverybody={isSharingPhoneNumberWithEverybody()} me={me} onCallCanceled={cancelActiveCall} onJoinCall={joinActiveCall} diff --git a/ts/components/CallingLobby.stories.tsx b/ts/components/CallingLobby.stories.tsx index 72e91af9d5..1540ff468d 100644 --- a/ts/components/CallingLobby.stories.tsx +++ b/ts/components/CallingLobby.stories.tsx @@ -20,6 +20,7 @@ import { } from '../test-both/helpers/getDefaultConversation'; import { CallingToastProvider } from './CallingToast'; import { CallMode } from '../types/Calling'; +import { getDefaultCallLinkConversation } from '../test-both/helpers/fakeCallLink'; const i18n = setupI18n('en', enMessages); @@ -33,15 +34,24 @@ const camera = { }, }; +const getConversation = (callMode: CallMode) => { + if (callMode === CallMode.Group) { + return getDefaultConversation({ + title: 'Tahoe Trip', + type: 'group', + }); + } + + if (callMode === CallMode.Adhoc) { + return getDefaultCallLinkConversation(); + } + + return getDefaultConversation(); +}; + const createProps = (overrideProps: Partial = {}): PropsType => { const callMode = overrideProps.callMode ?? CallMode.Direct; - const conversation = - callMode === CallMode.Group - ? getDefaultConversation({ - title: 'Tahoe Trip', - type: 'group', - }) - : getDefaultConversation(); + const conversation = getConversation(callMode); return { availableCameras: overrideProps.availableCameras || [camera], @@ -55,9 +65,13 @@ const createProps = (overrideProps: Partial = {}): PropsType => { hasLocalAudio: overrideProps.hasLocalAudio ?? true, hasLocalVideo: overrideProps.hasLocalVideo ?? false, i18n, + isAdhocAdminApprovalRequired: + overrideProps.isAdhocAdminApprovalRequired ?? false, isAdhocJoinRequestPending: false, isConversationTooBigToRing: false, isCallFull: overrideProps.isCallFull ?? false, + isSharingPhoneNumberWithEverybody: + overrideProps.isSharingPhoneNumberWithEverybody ?? false, me: overrideProps.me || getDefaultConversation({ @@ -206,3 +220,20 @@ export function GroupCallWith0PeekedParticipantsBigGroup(): JSX.Element { }); return ; } + +export function CallLink(): JSX.Element { + const props = createProps({ + callMode: CallMode.Adhoc, + }); + return ; +} + +// Due to storybook font loading, if you directly load this story then +// the button width is not calculated correctly +export function CallLinkAdminApproval(): JSX.Element { + const props = createProps({ + callMode: CallMode.Adhoc, + isAdhocAdminApprovalRequired: true, + }); + return ; +} diff --git a/ts/components/CallingLobby.tsx b/ts/components/CallingLobby.tsx index fb338e695f..c2838a62dd 100644 --- a/ts/components/CallingLobby.tsx +++ b/ts/components/CallingLobby.tsx @@ -28,7 +28,6 @@ import type { ConversationType } from '../state/ducks/conversations'; import { useCallingToasts } from './CallingToast'; import { CallingButtonToastsContainer } from './CallingToastManager'; import { isGroupOrAdhocCallMode } from '../util/isGroupOrAdhocCall'; -import { isSharingPhoneNumberWithEverybody } from '../util/phoneNumberSharingMode'; export type PropsType = { availableCameras: Array; @@ -59,9 +58,11 @@ export type PropsType = { hasLocalAudio: boolean; hasLocalVideo: boolean; i18n: LocalizerType; + isAdhocAdminApprovalRequired: boolean; isAdhocJoinRequestPending: boolean; isConversationTooBigToRing: boolean; isCallFull?: boolean; + isSharingPhoneNumberWithEverybody: boolean; me: Readonly< Pick >; @@ -87,9 +88,11 @@ export function CallingLobby({ hasLocalAudio, hasLocalVideo, i18n, + isAdhocAdminApprovalRequired, isAdhocJoinRequestPending, isCallFull = false, isConversationTooBigToRing, + isSharingPhoneNumberWithEverybody, me, onCallCanceled, onJoinCall, @@ -214,6 +217,8 @@ export function CallingLobby({ callingLobbyJoinButtonVariant = CallingLobbyJoinButtonVariant.CallIsFull; } else if (isCallConnecting) { callingLobbyJoinButtonVariant = CallingLobbyJoinButtonVariant.Loading; + } else if (isAdhocAdminApprovalRequired) { + callingLobbyJoinButtonVariant = CallingLobbyJoinButtonVariant.AskToJoin; } else if (peekedParticipants.length || callMode === CallMode.Adhoc) { callingLobbyJoinButtonVariant = CallingLobbyJoinButtonVariant.Join; } else { @@ -303,7 +308,7 @@ export function CallingLobby({ {callMode === CallMode.Adhoc && (
- {isSharingPhoneNumberWithEverybody() + {isSharingPhoneNumberWithEverybody ? i18n('icu:CallingLobby__CallLinkNotice--phone-sharing') : i18n('icu:CallingLobby__CallLinkNotice')}
diff --git a/ts/components/CallingLobbyJoinButton.tsx b/ts/components/CallingLobbyJoinButton.tsx index 1dad068178..40b2f8af48 100644 --- a/ts/components/CallingLobbyJoinButton.tsx +++ b/ts/components/CallingLobbyJoinButton.tsx @@ -14,6 +14,7 @@ export enum CallingLobbyJoinButtonVariant { Join = 'Join', Loading = 'Loading', Start = 'Start', + AskToJoin = 'AskToJoin', } type PropsType = { @@ -55,6 +56,9 @@ export function CallingLobbyJoinButton({ [CallingLobbyJoinButtonVariant.Start]: i18n( 'icu:CallingLobbyJoinButton--start' ), + [CallingLobbyJoinButtonVariant.AskToJoin]: i18n( + 'icu:CallingLobbyJoinButton--ask-to-join' + ), }; return ( diff --git a/ts/test-both/helpers/fakeCallLink.ts b/ts/test-both/helpers/fakeCallLink.ts index 0b5304bd47..052a408b48 100644 --- a/ts/test-both/helpers/fakeCallLink.ts +++ b/ts/test-both/helpers/fakeCallLink.ts @@ -1,5 +1,6 @@ // Copyright 2024 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only +import type { CallingConversationType } from '../../types/Calling'; import type { CallLinkType } from '../../types/CallLink'; import { CallLinkRestrictions } from '../../types/CallLink'; import { MONTH } from '../../util/durations/constants'; @@ -24,3 +25,21 @@ export const FAKE_CALL_LINK_WITH_ADMIN_KEY: CallLinkType = { roomId: 'c097eb04cc278d6bc7ed9fb2ddeac00dc9646ae6ddb38513dad9a8a4fe3c38f4', rootKey: 'bpmc-mrgn-hntf-mffd-mndd-xbxk-zmgq-qszg', }; + +export function getDefaultCallLinkConversation( + callLinkOverrideProps: Partial = {} +): CallingConversationType { + const { roomId: id, name: title } = { + ...FAKE_CALL_LINK, + ...callLinkOverrideProps, + }; + return { + id, + type: 'callLink', + isMe: false, + title, + sharedGroupNames: [], + acceptedMessageRequest: true, + badges: [], + }; +}