ConversationView: Move call/mute functions into redux
This commit is contained in:
parent
8fe51cc854
commit
92a512a16d
17 changed files with 353 additions and 287 deletions
|
@ -1,20 +0,0 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import type { LocalizerType } from '../types/Util';
|
|
||||||
import { Toast } from './Toast';
|
|
||||||
|
|
||||||
type PropsType = {
|
|
||||||
i18n: LocalizerType;
|
|
||||||
onClose: () => unknown;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function ToastCannotStartGroupCall({
|
|
||||||
i18n,
|
|
||||||
onClose,
|
|
||||||
}: PropsType): JSX.Element {
|
|
||||||
return (
|
|
||||||
<Toast onClose={onClose}>{i18n('GroupV2--cannot-start-group-call')}</Toast>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -30,72 +30,6 @@ export function ToastManager({
|
||||||
}
|
}
|
||||||
|
|
||||||
const { toastType } = toast;
|
const { toastType } = toast;
|
||||||
if (toastType === ToastType.Error) {
|
|
||||||
return (
|
|
||||||
<Toast
|
|
||||||
autoDismissDisabled
|
|
||||||
onClose={hideToast}
|
|
||||||
toastAction={{
|
|
||||||
label: i18n('Toast--error--action'),
|
|
||||||
onClick: () => window.showDebugLog(),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{i18n('Toast--error')}
|
|
||||||
</Toast>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toastType === ToastType.MessageBodyTooLong) {
|
|
||||||
return <ToastMessageBodyTooLong i18n={i18n} onClose={hideToast} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toastType === ToastType.StoryReact) {
|
|
||||||
return (
|
|
||||||
<Toast onClose={hideToast} timeout={SHORT_TIMEOUT}>
|
|
||||||
{i18n('Stories__toast--sending-reaction')}
|
|
||||||
</Toast>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toastType === ToastType.StoryReply) {
|
|
||||||
return (
|
|
||||||
<Toast onClose={hideToast} timeout={SHORT_TIMEOUT}>
|
|
||||||
{i18n('Stories__toast--sending-reply')}
|
|
||||||
</Toast>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toastType === ToastType.StoryMuted) {
|
|
||||||
return (
|
|
||||||
<Toast onClose={hideToast} timeout={SHORT_TIMEOUT}>
|
|
||||||
{i18n('Stories__toast--hasNoSound')}
|
|
||||||
</Toast>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toastType === ToastType.StoryVideoTooLong) {
|
|
||||||
return (
|
|
||||||
<Toast onClose={hideToast}>
|
|
||||||
{i18n('StoryCreator__error--video-too-long')}
|
|
||||||
</Toast>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toastType === ToastType.StoryVideoUnsupported) {
|
|
||||||
return (
|
|
||||||
<Toast onClose={hideToast}>
|
|
||||||
{i18n('StoryCreator__error--video-unsupported')}
|
|
||||||
</Toast>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toastType === ToastType.StoryVideoError) {
|
|
||||||
return (
|
|
||||||
<Toast onClose={hideToast}>
|
|
||||||
{i18n('StoryCreator__error--video-error')}
|
|
||||||
</Toast>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toastType === ToastType.AddingUserToGroup) {
|
if (toastType === ToastType.AddingUserToGroup) {
|
||||||
return (
|
return (
|
||||||
|
@ -108,21 +42,10 @@ export function ToastManager({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toastType === ToastType.UserAddedToGroup) {
|
if (toastType === ToastType.CannotStartGroupCall) {
|
||||||
return (
|
return (
|
||||||
<Toast onClose={hideToast}>
|
<Toast onClose={hideToast}>
|
||||||
{i18n(
|
{i18n('GroupV2--cannot-start-group-call', toast.parameters)}
|
||||||
'AddUserToAnotherGroupModal__toast--user-added-to-group',
|
|
||||||
toast.parameters
|
|
||||||
)}
|
|
||||||
</Toast>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toastType === ToastType.FailedToDeleteUsername) {
|
|
||||||
return (
|
|
||||||
<Toast onClose={hideToast}>
|
|
||||||
{i18n('ProfileEditor--username--delete-general-error')}
|
|
||||||
</Toast>
|
</Toast>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -147,5 +70,91 @@ export function ToastManager({
|
||||||
return <Toast onClose={hideToast}>{i18n('deleteForEveryoneFailed')}</Toast>;
|
return <Toast onClose={hideToast}>{i18n('deleteForEveryoneFailed')}</Toast>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (toastType === ToastType.Error) {
|
||||||
|
return (
|
||||||
|
<Toast
|
||||||
|
autoDismissDisabled
|
||||||
|
onClose={hideToast}
|
||||||
|
toastAction={{
|
||||||
|
label: i18n('Toast--error--action'),
|
||||||
|
onClick: () => window.showDebugLog(),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{i18n('Toast--error')}
|
||||||
|
</Toast>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toastType === ToastType.FailedToDeleteUsername) {
|
||||||
|
return (
|
||||||
|
<Toast onClose={hideToast}>
|
||||||
|
{i18n('ProfileEditor--username--delete-general-error')}
|
||||||
|
</Toast>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toastType === ToastType.MessageBodyTooLong) {
|
||||||
|
return <ToastMessageBodyTooLong i18n={i18n} onClose={hideToast} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toastType === ToastType.StoryMuted) {
|
||||||
|
return (
|
||||||
|
<Toast onClose={hideToast} timeout={SHORT_TIMEOUT}>
|
||||||
|
{i18n('Stories__toast--hasNoSound')}
|
||||||
|
</Toast>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toastType === ToastType.StoryReact) {
|
||||||
|
return (
|
||||||
|
<Toast onClose={hideToast} timeout={SHORT_TIMEOUT}>
|
||||||
|
{i18n('Stories__toast--sending-reaction')}
|
||||||
|
</Toast>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toastType === ToastType.StoryReply) {
|
||||||
|
return (
|
||||||
|
<Toast onClose={hideToast} timeout={SHORT_TIMEOUT}>
|
||||||
|
{i18n('Stories__toast--sending-reply')}
|
||||||
|
</Toast>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toastType === ToastType.StoryVideoError) {
|
||||||
|
return (
|
||||||
|
<Toast onClose={hideToast}>
|
||||||
|
{i18n('StoryCreator__error--video-error')}
|
||||||
|
</Toast>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toastType === ToastType.StoryVideoTooLong) {
|
||||||
|
return (
|
||||||
|
<Toast onClose={hideToast}>
|
||||||
|
{i18n('StoryCreator__error--video-too-long')}
|
||||||
|
</Toast>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toastType === ToastType.StoryVideoUnsupported) {
|
||||||
|
return (
|
||||||
|
<Toast onClose={hideToast}>
|
||||||
|
{i18n('StoryCreator__error--video-unsupported')}
|
||||||
|
</Toast>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toastType === ToastType.UserAddedToGroup) {
|
||||||
|
return (
|
||||||
|
<Toast onClose={hideToast}>
|
||||||
|
{i18n(
|
||||||
|
'AddUserToAnotherGroupModal__toast--user-added-to-group',
|
||||||
|
toast.parameters
|
||||||
|
)}
|
||||||
|
</Toast>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
throw missingCaseError(toastType);
|
throw missingCaseError(toastType);
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,6 @@ const commonProps = {
|
||||||
setDisappearingMessages: action('setDisappearingMessages'),
|
setDisappearingMessages: action('setDisappearingMessages'),
|
||||||
destroyMessages: action('destroyMessages'),
|
destroyMessages: action('destroyMessages'),
|
||||||
onSearchInConversation: action('onSearchInConversation'),
|
onSearchInConversation: action('onSearchInConversation'),
|
||||||
onSetMuteNotifications: action('onSetMuteNotifications'),
|
|
||||||
onOutgoingAudioCallInConversation: action(
|
onOutgoingAudioCallInConversation: action(
|
||||||
'onOutgoingAudioCallInConversation'
|
'onOutgoingAudioCallInConversation'
|
||||||
),
|
),
|
||||||
|
@ -57,6 +56,7 @@ const commonProps = {
|
||||||
onMarkUnread: action('onMarkUnread'),
|
onMarkUnread: action('onMarkUnread'),
|
||||||
onMoveToInbox: action('onMoveToInbox'),
|
onMoveToInbox: action('onMoveToInbox'),
|
||||||
onSetPin: action('onSetPin'),
|
onSetPin: action('onSetPin'),
|
||||||
|
setMuteExpiration: action('onSetMuteNotifications'),
|
||||||
viewUserStories: action('viewUserStories'),
|
viewUserStories: action('viewUserStories'),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -80,11 +80,10 @@ export type PropsDataType = {
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type PropsActionsType = {
|
export type PropsActionsType = {
|
||||||
onSetMuteNotifications: (seconds: number) => void;
|
|
||||||
destroyMessages: (conversationId: string) => void;
|
destroyMessages: (conversationId: string) => void;
|
||||||
onSearchInConversation: () => void;
|
onSearchInConversation: () => void;
|
||||||
onOutgoingAudioCallInConversation: () => void;
|
onOutgoingAudioCallInConversation: (conversationId: string) => void;
|
||||||
onOutgoingVideoCallInConversation: () => void;
|
onOutgoingVideoCallInConversation: (conversationId: string) => void;
|
||||||
onSetPin: (value: boolean) => void;
|
onSetPin: (value: boolean) => void;
|
||||||
|
|
||||||
onShowConversationDetails: () => void;
|
onShowConversationDetails: () => void;
|
||||||
|
@ -95,6 +94,7 @@ export type PropsActionsType = {
|
||||||
onArchive: () => void;
|
onArchive: () => void;
|
||||||
onMarkUnread: () => void;
|
onMarkUnread: () => void;
|
||||||
onMoveToInbox: () => void;
|
onMoveToInbox: () => void;
|
||||||
|
setMuteExpiration: (conversationId: string, seconds: number) => void;
|
||||||
setDisappearingMessages: (
|
setDisappearingMessages: (
|
||||||
conversationId: string,
|
conversationId: string,
|
||||||
seconds: DurationInSeconds
|
seconds: DurationInSeconds
|
||||||
|
@ -349,12 +349,12 @@ export class ConversationHeader extends React.Component<PropsType, StateType> {
|
||||||
onArchive,
|
onArchive,
|
||||||
onMarkUnread,
|
onMarkUnread,
|
||||||
onMoveToInbox,
|
onMoveToInbox,
|
||||||
onSetMuteNotifications,
|
|
||||||
onSetPin,
|
onSetPin,
|
||||||
onShowAllMedia,
|
onShowAllMedia,
|
||||||
onShowConversationDetails,
|
onShowConversationDetails,
|
||||||
onShowGroupMembers,
|
onShowGroupMembers,
|
||||||
setDisappearingMessages,
|
setDisappearingMessages,
|
||||||
|
setMuteExpiration,
|
||||||
type,
|
type,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
@ -371,7 +371,7 @@ export class ConversationHeader extends React.Component<PropsType, StateType> {
|
||||||
{isMuted ? (
|
{isMuted ? (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onSetMuteNotifications(0);
|
setMuteExpiration(id, 0);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{i18n('unmute')}
|
{i18n('unmute')}
|
||||||
|
@ -379,7 +379,7 @@ export class ConversationHeader extends React.Component<PropsType, StateType> {
|
||||||
) : (
|
) : (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onSetMuteNotifications(Number.MAX_SAFE_INTEGER);
|
setMuteExpiration(id, Number.MAX_SAFE_INTEGER);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{i18n('muteAlways')}
|
{i18n('muteAlways')}
|
||||||
|
@ -465,7 +465,7 @@ export class ConversationHeader extends React.Component<PropsType, StateType> {
|
||||||
key={item.name}
|
key={item.name}
|
||||||
disabled={item.disabled}
|
disabled={item.disabled}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onSetMuteNotifications(item.value);
|
setMuteExpiration(id, item.value);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{item.name}
|
{item.name}
|
||||||
|
@ -676,6 +676,7 @@ export class ConversationHeader extends React.Component<PropsType, StateType> {
|
||||||
announcementsOnly={announcementsOnly}
|
announcementsOnly={announcementsOnly}
|
||||||
areWeAdmin={areWeAdmin}
|
areWeAdmin={areWeAdmin}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
|
id={id}
|
||||||
isNarrow={isNarrow}
|
isNarrow={isNarrow}
|
||||||
onOutgoingAudioCallInConversation={
|
onOutgoingAudioCallInConversation={
|
||||||
onOutgoingAudioCallInConversation
|
onOutgoingAudioCallInConversation
|
||||||
|
@ -702,6 +703,7 @@ function OutgoingCallButtons({
|
||||||
announcementsOnly,
|
announcementsOnly,
|
||||||
areWeAdmin,
|
areWeAdmin,
|
||||||
i18n,
|
i18n,
|
||||||
|
id,
|
||||||
isNarrow,
|
isNarrow,
|
||||||
onOutgoingAudioCallInConversation,
|
onOutgoingAudioCallInConversation,
|
||||||
onOutgoingVideoCallInConversation,
|
onOutgoingVideoCallInConversation,
|
||||||
|
@ -712,6 +714,7 @@ function OutgoingCallButtons({
|
||||||
| 'announcementsOnly'
|
| 'announcementsOnly'
|
||||||
| 'areWeAdmin'
|
| 'areWeAdmin'
|
||||||
| 'i18n'
|
| 'i18n'
|
||||||
|
| 'id'
|
||||||
| 'onOutgoingAudioCallInConversation'
|
| 'onOutgoingAudioCallInConversation'
|
||||||
| 'onOutgoingVideoCallInConversation'
|
| 'onOutgoingVideoCallInConversation'
|
||||||
| 'outgoingCallButtonStyle'
|
| 'outgoingCallButtonStyle'
|
||||||
|
@ -729,14 +732,14 @@ function OutgoingCallButtons({
|
||||||
: undefined
|
: undefined
|
||||||
)}
|
)}
|
||||||
disabled={showBackButton}
|
disabled={showBackButton}
|
||||||
onClick={onOutgoingVideoCallInConversation}
|
onClick={() => onOutgoingVideoCallInConversation(id)}
|
||||||
type="button"
|
type="button"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const startCallShortcuts = useStartCallShortcuts(
|
const startCallShortcuts = useStartCallShortcuts(
|
||||||
onOutgoingAudioCallInConversation,
|
() => onOutgoingAudioCallInConversation(id),
|
||||||
onOutgoingVideoCallInConversation
|
() => onOutgoingVideoCallInConversation(id)
|
||||||
);
|
);
|
||||||
useKeyboardShortcuts(startCallShortcuts);
|
useKeyboardShortcuts(startCallShortcuts);
|
||||||
|
|
||||||
|
@ -751,7 +754,7 @@ function OutgoingCallButtons({
|
||||||
{videoButton}
|
{videoButton}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onOutgoingAudioCallInConversation}
|
onClick={() => onOutgoingAudioCallInConversation(id)}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'module-ConversationHeader__button',
|
'module-ConversationHeader__button',
|
||||||
'module-ConversationHeader__button--audio',
|
'module-ConversationHeader__button--audio',
|
||||||
|
@ -772,7 +775,7 @@ function OutgoingCallButtons({
|
||||||
showBackButton ? null : 'module-ConversationHeader__button--show'
|
showBackButton ? null : 'module-ConversationHeader__button--show'
|
||||||
)}
|
)}
|
||||||
disabled={showBackButton}
|
disabled={showBackButton}
|
||||||
onClick={onOutgoingVideoCallInConversation}
|
onClick={() => onOutgoingVideoCallInConversation(id)}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
{isNarrow ? null : i18n('joinOngoingCall')}
|
{isNarrow ? null : i18n('joinOngoingCall')}
|
||||||
|
|
|
@ -101,9 +101,6 @@ export type StateProps = {
|
||||||
onUnblock: () => void;
|
onUnblock: () => void;
|
||||||
theme: ThemeType;
|
theme: ThemeType;
|
||||||
userAvatarData: Array<AvatarDataType>;
|
userAvatarData: Array<AvatarDataType>;
|
||||||
setMuteExpiration: (muteExpiresAt: undefined | number) => unknown;
|
|
||||||
onOutgoingAudioCallInConversation: () => unknown;
|
|
||||||
onOutgoingVideoCallInConversation: () => unknown;
|
|
||||||
renderChooseGroupMembersModal: (
|
renderChooseGroupMembersModal: (
|
||||||
props: SmartChooseGroupMembersModalPropsType
|
props: SmartChooseGroupMembersModalPropsType
|
||||||
) => JSX.Element;
|
) => JSX.Element;
|
||||||
|
@ -115,10 +112,13 @@ export type StateProps = {
|
||||||
type ActionProps = {
|
type ActionProps = {
|
||||||
deleteAvatarFromDisk: DeleteAvatarFromDiskActionType;
|
deleteAvatarFromDisk: DeleteAvatarFromDiskActionType;
|
||||||
loadRecentMediaItems: (id: string, limit: number) => void;
|
loadRecentMediaItems: (id: string, limit: number) => void;
|
||||||
|
onOutgoingAudioCallInConversation: (conversationId: string) => unknown;
|
||||||
|
onOutgoingVideoCallInConversation: (conversationId: string) => unknown;
|
||||||
replaceAvatar: ReplaceAvatarActionType;
|
replaceAvatar: ReplaceAvatarActionType;
|
||||||
saveAvatarToDisk: SaveAvatarToDiskActionType;
|
saveAvatarToDisk: SaveAvatarToDiskActionType;
|
||||||
searchInConversation: (id: string) => unknown;
|
searchInConversation: (id: string) => unknown;
|
||||||
setDisappearingMessages: (id: string, seconds: DurationInSeconds) => void;
|
setDisappearingMessages: (id: string, seconds: DurationInSeconds) => void;
|
||||||
|
setMuteExpiration: (id: string, muteExpiresAt: undefined | number) => unknown;
|
||||||
showContactModal: (contactId: string, conversationId?: string) => void;
|
showContactModal: (contactId: string, conversationId?: string) => void;
|
||||||
showConversation: ShowConversationType;
|
showConversation: ShowConversationType;
|
||||||
toggleAddUserToAnotherGroupModal: (contactId?: string) => void;
|
toggleAddUserToAnotherGroupModal: (contactId?: string) => void;
|
||||||
|
@ -291,6 +291,7 @@ export function ConversationDetails({
|
||||||
modalNode = (
|
modalNode = (
|
||||||
<ConversationNotificationsModal
|
<ConversationNotificationsModal
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
|
id={conversation.id}
|
||||||
muteExpiresAt={conversation.muteExpiresAt}
|
muteExpiresAt={conversation.muteExpiresAt}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setModalState(ModalState.NothingOpen);
|
setModalState(ModalState.NothingOpen);
|
||||||
|
@ -305,7 +306,7 @@ export function ConversationDetails({
|
||||||
dialogName="ConversationDetails.unmuteNotifications"
|
dialogName="ConversationDetails.unmuteNotifications"
|
||||||
actions={[
|
actions={[
|
||||||
{
|
{
|
||||||
action: () => setMuteExpiration(0),
|
action: () => setMuteExpiration(conversation.id, 0),
|
||||||
style: 'affirmative',
|
style: 'affirmative',
|
||||||
text: i18n('unmute'),
|
text: i18n('unmute'),
|
||||||
},
|
},
|
||||||
|
@ -354,14 +355,16 @@ export function ConversationDetails({
|
||||||
<ConversationDetailsCallButton
|
<ConversationDetailsCallButton
|
||||||
disabled={hasActiveCall}
|
disabled={hasActiveCall}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
onClick={onOutgoingVideoCallInConversation}
|
onClick={() => onOutgoingVideoCallInConversation(conversation.id)}
|
||||||
type="video"
|
type="video"
|
||||||
/>
|
/>
|
||||||
{!isGroup && (
|
{!isGroup && (
|
||||||
<ConversationDetailsCallButton
|
<ConversationDetailsCallButton
|
||||||
disabled={hasActiveCall}
|
disabled={hasActiveCall}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
onClick={onOutgoingAudioCallInConversation}
|
onClick={() =>
|
||||||
|
onOutgoingAudioCallInConversation(conversation.id)
|
||||||
|
}
|
||||||
type="audio"
|
type="audio"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -12,13 +12,18 @@ import { Button, ButtonVariant } from '../../Button';
|
||||||
|
|
||||||
type PropsType = {
|
type PropsType = {
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
|
id: string;
|
||||||
muteExpiresAt: undefined | number;
|
muteExpiresAt: undefined | number;
|
||||||
onClose: () => unknown;
|
onClose: () => unknown;
|
||||||
setMuteExpiration: (muteExpiresAt: undefined | number) => unknown;
|
setMuteExpiration: (
|
||||||
|
conversationId: string,
|
||||||
|
muteExpiresAt: undefined | number
|
||||||
|
) => unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ConversationNotificationsModal({
|
export function ConversationNotificationsModal({
|
||||||
i18n,
|
i18n,
|
||||||
|
id,
|
||||||
muteExpiresAt,
|
muteExpiresAt,
|
||||||
onClose,
|
onClose,
|
||||||
setMuteExpiration,
|
setMuteExpiration,
|
||||||
|
@ -40,7 +45,7 @@ export function ConversationNotificationsModal({
|
||||||
muteExpirationValue,
|
muteExpirationValue,
|
||||||
'NotificationSettings: mute ms was not an integer'
|
'NotificationSettings: mute ms was not an integer'
|
||||||
);
|
);
|
||||||
setMuteExpiration(ms);
|
setMuteExpiration(id, ms);
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ export default {
|
||||||
};
|
};
|
||||||
|
|
||||||
const getCommonProps = () => ({
|
const getCommonProps = () => ({
|
||||||
|
id: 'conversation-id',
|
||||||
muteExpiresAt: undefined,
|
muteExpiresAt: undefined,
|
||||||
conversationType: 'group' as const,
|
conversationType: 'group' as const,
|
||||||
dontNotifyForMentionsIfMuted: false,
|
dontNotifyForMentionsIfMuted: false,
|
||||||
|
|
|
@ -15,17 +15,23 @@ import { parseIntOrThrow } from '../../../util/parseIntOrThrow';
|
||||||
import { useUniqueId } from '../../../hooks/useUniqueId';
|
import { useUniqueId } from '../../../hooks/useUniqueId';
|
||||||
|
|
||||||
type PropsType = {
|
type PropsType = {
|
||||||
|
id: string;
|
||||||
conversationType: ConversationTypeType;
|
conversationType: ConversationTypeType;
|
||||||
dontNotifyForMentionsIfMuted: boolean;
|
dontNotifyForMentionsIfMuted: boolean;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
muteExpiresAt: undefined | number;
|
muteExpiresAt: undefined | number;
|
||||||
setDontNotifyForMentionsIfMuted: (
|
setDontNotifyForMentionsIfMuted: (
|
||||||
|
conversationId: string,
|
||||||
dontNotifyForMentionsIfMuted: boolean
|
dontNotifyForMentionsIfMuted: boolean
|
||||||
) => unknown;
|
) => unknown;
|
||||||
setMuteExpiration: (muteExpiresAt: undefined | number) => unknown;
|
setMuteExpiration: (
|
||||||
|
conversationId: string,
|
||||||
|
muteExpiresAt: undefined | number
|
||||||
|
) => unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ConversationNotificationsSettings({
|
export function ConversationNotificationsSettings({
|
||||||
|
id,
|
||||||
conversationType,
|
conversationType,
|
||||||
dontNotifyForMentionsIfMuted,
|
dontNotifyForMentionsIfMuted,
|
||||||
i18n,
|
i18n,
|
||||||
|
@ -62,11 +68,11 @@ export function ConversationNotificationsSettings({
|
||||||
rawValue,
|
rawValue,
|
||||||
'NotificationSettings: mute ms was not an integer'
|
'NotificationSettings: mute ms was not an integer'
|
||||||
);
|
);
|
||||||
setMuteExpiration(ms);
|
setMuteExpiration(id, ms);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onChangeDontNotifyForMentionsIfMuted = (rawValue: string) => {
|
const onChangeDontNotifyForMentionsIfMuted = (rawValue: string) => {
|
||||||
setDontNotifyForMentionsIfMuted(rawValue === 'yes');
|
setDontNotifyForMentionsIfMuted(id, rawValue === 'yes');
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -47,6 +47,10 @@ import * as log from '../../logging/log';
|
||||||
import { strictAssert } from '../../util/assert';
|
import { strictAssert } from '../../util/assert';
|
||||||
import { waitForOnline } from '../../util/waitForOnline';
|
import { waitForOnline } from '../../util/waitForOnline';
|
||||||
import * as mapUtil from '../../util/mapUtil';
|
import * as mapUtil from '../../util/mapUtil';
|
||||||
|
import { isCallSafe } from '../../util/isCallSafe';
|
||||||
|
import { isDirectConversation } from '../../util/whatTypeOfConversation';
|
||||||
|
import { SHOW_TOAST, ToastType } from './toast';
|
||||||
|
import type { ShowToastActionType } from './toast';
|
||||||
|
|
||||||
// State
|
// State
|
||||||
|
|
||||||
|
@ -1189,6 +1193,106 @@ function setOutgoingRing(payload: boolean): SetOutgoingRingActionType {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onOutgoingVideoCallInConversation(
|
||||||
|
conversationId: string
|
||||||
|
): ThunkAction<
|
||||||
|
void,
|
||||||
|
RootStateType,
|
||||||
|
unknown,
|
||||||
|
StartCallingLobbyActionType | ShowToastActionType
|
||||||
|
> {
|
||||||
|
return async (dispatch, getState) => {
|
||||||
|
const conversation = window.ConversationController.get(conversationId);
|
||||||
|
if (!conversation) {
|
||||||
|
throw new Error(
|
||||||
|
`onOutgoingVideoCallInConversation: No conversation found for conversation ${conversationId}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info('onOutgoingVideoCallInConversation: about to start a video call');
|
||||||
|
|
||||||
|
// if it's a group call on an announcementsOnly group
|
||||||
|
// only allow join if the call has already been started (presumably by the admin)
|
||||||
|
if (conversation.get('announcementsOnly') && !conversation.areWeAdmin()) {
|
||||||
|
const call = getOwn(
|
||||||
|
getState().calling.callsByConversation,
|
||||||
|
conversationId
|
||||||
|
);
|
||||||
|
|
||||||
|
// technically not necessary, but isAnybodyElseInGroupCall requires it
|
||||||
|
const ourUuid = window.storage.user.getCheckedUuid().toString();
|
||||||
|
|
||||||
|
const isOngoingGroupCall =
|
||||||
|
call &&
|
||||||
|
ourUuid &&
|
||||||
|
call.callMode === CallMode.Group &&
|
||||||
|
call.peekInfo &&
|
||||||
|
isAnybodyElseInGroupCall(call.peekInfo, ourUuid);
|
||||||
|
|
||||||
|
if (!isOngoingGroupCall) {
|
||||||
|
dispatch({
|
||||||
|
type: SHOW_TOAST,
|
||||||
|
payload: {
|
||||||
|
toastType: ToastType.CannotStartGroupCall,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await isCallSafe(conversation.attributes)) {
|
||||||
|
log.info(
|
||||||
|
'onOutgoingVideoCallInConversation: call is deemed "safe". Making call'
|
||||||
|
);
|
||||||
|
startCallingLobby({
|
||||||
|
conversationId,
|
||||||
|
isVideoCall: true,
|
||||||
|
})(dispatch, getState, undefined);
|
||||||
|
log.info('onOutgoingVideoCallInConversation: started the call');
|
||||||
|
} else {
|
||||||
|
log.info(
|
||||||
|
'onOutgoingVideoCallInConversation: call is deemed "unsafe". Stopping'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function onOutgoingAudioCallInConversation(
|
||||||
|
conversationId: string
|
||||||
|
): ThunkAction<void, RootStateType, unknown, StartCallingLobbyActionType> {
|
||||||
|
return async (dispatch, getState) => {
|
||||||
|
const conversation = window.ConversationController.get(conversationId);
|
||||||
|
if (!conversation) {
|
||||||
|
throw new Error(
|
||||||
|
`onOutgoingAudioCallInConversation: No conversation found for conversation ${conversationId}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isDirectConversation(conversation.attributes)) {
|
||||||
|
throw new Error(
|
||||||
|
`onOutgoingAudioCallInConversation: Conversation ${conversation.idForLogging()} is not 1:1`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info('onOutgoingAudioCallInConversation: about to start an audio call');
|
||||||
|
|
||||||
|
if (await isCallSafe(conversation.attributes)) {
|
||||||
|
log.info(
|
||||||
|
'onOutgoingAudioCallInConversation: call is deemed "safe". Making call'
|
||||||
|
);
|
||||||
|
startCallingLobby({
|
||||||
|
conversationId,
|
||||||
|
isVideoCall: false,
|
||||||
|
})(dispatch, getState, undefined);
|
||||||
|
log.info('onOutgoingAudioCallInConversation: started the call');
|
||||||
|
} else {
|
||||||
|
log.info(
|
||||||
|
'onOutgoingAudioCallInConversation: call is deemed "unsafe". Stopping'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function startCallingLobby({
|
function startCallingLobby({
|
||||||
conversationId,
|
conversationId,
|
||||||
isVideoCall,
|
isVideoCall,
|
||||||
|
@ -1346,6 +1450,8 @@ export const actions = {
|
||||||
hangUpActiveCall,
|
hangUpActiveCall,
|
||||||
keyChangeOk,
|
keyChangeOk,
|
||||||
keyChanged,
|
keyChanged,
|
||||||
|
onOutgoingVideoCallInConversation,
|
||||||
|
onOutgoingAudioCallInConversation,
|
||||||
openSystemPreferencesAction,
|
openSystemPreferencesAction,
|
||||||
outgoingCall,
|
outgoingCall,
|
||||||
peekGroupCallForTheFirstTime,
|
peekGroupCallForTheFirstTime,
|
||||||
|
|
|
@ -897,9 +897,11 @@ export const actions = {
|
||||||
setComposeGroupName,
|
setComposeGroupName,
|
||||||
setComposeSearchTerm,
|
setComposeSearchTerm,
|
||||||
setDisappearingMessages,
|
setDisappearingMessages,
|
||||||
|
setDontNotifyForMentionsIfMuted,
|
||||||
setIsFetchingUUID,
|
setIsFetchingUUID,
|
||||||
setIsNearBottom,
|
setIsNearBottom,
|
||||||
setMessageLoadingState,
|
setMessageLoadingState,
|
||||||
|
setMuteExpiration,
|
||||||
setPreJoinConversation,
|
setPreJoinConversation,
|
||||||
setSelectedConversationHeaderTitle,
|
setSelectedConversationHeaderTitle,
|
||||||
setSelectedConversationPanelDepth,
|
setSelectedConversationPanelDepth,
|
||||||
|
@ -943,7 +945,7 @@ async function getAvatarsAndUpdateConversation(
|
||||||
): Promise<Array<AvatarDataType>> {
|
): Promise<Array<AvatarDataType>> {
|
||||||
const conversation = window.ConversationController.get(conversationId);
|
const conversation = window.ConversationController.get(conversationId);
|
||||||
if (!conversation) {
|
if (!conversation) {
|
||||||
throw new Error('No conversation found');
|
throw new Error('getAvatarsAndUpdateConversation: No conversation found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const { conversationLookup } = conversations;
|
const { conversationLookup } = conversations;
|
||||||
|
@ -1004,7 +1006,7 @@ function changeHasGroupLink(
|
||||||
return async dispatch => {
|
return async dispatch => {
|
||||||
const conversation = window.ConversationController.get(conversationId);
|
const conversation = window.ConversationController.get(conversationId);
|
||||||
if (!conversation) {
|
if (!conversation) {
|
||||||
throw new Error('No conversation found');
|
throw new Error('changeHasGroupLink: No conversation found');
|
||||||
}
|
}
|
||||||
|
|
||||||
await longRunningTaskWrapper({
|
await longRunningTaskWrapper({
|
||||||
|
@ -1026,7 +1028,7 @@ function setAnnouncementsOnly(
|
||||||
return async dispatch => {
|
return async dispatch => {
|
||||||
const conversation = window.ConversationController.get(conversationId);
|
const conversation = window.ConversationController.get(conversationId);
|
||||||
if (!conversation) {
|
if (!conversation) {
|
||||||
throw new Error('No conversation found');
|
throw new Error('setAnnouncementsOnly: No conversation found');
|
||||||
}
|
}
|
||||||
|
|
||||||
await longRunningTaskWrapper({
|
await longRunningTaskWrapper({
|
||||||
|
@ -1048,7 +1050,7 @@ function setAccessControlMembersSetting(
|
||||||
return async dispatch => {
|
return async dispatch => {
|
||||||
const conversation = window.ConversationController.get(conversationId);
|
const conversation = window.ConversationController.get(conversationId);
|
||||||
if (!conversation) {
|
if (!conversation) {
|
||||||
throw new Error('No conversation found');
|
throw new Error('setAccessControlMembersSetting: No conversation found');
|
||||||
}
|
}
|
||||||
|
|
||||||
await longRunningTaskWrapper({
|
await longRunningTaskWrapper({
|
||||||
|
@ -1070,7 +1072,9 @@ function setAccessControlAttributesSetting(
|
||||||
return async dispatch => {
|
return async dispatch => {
|
||||||
const conversation = window.ConversationController.get(conversationId);
|
const conversation = window.ConversationController.get(conversationId);
|
||||||
if (!conversation) {
|
if (!conversation) {
|
||||||
throw new Error('No conversation found');
|
throw new Error(
|
||||||
|
'setAccessControlAttributesSetting: No conversation found'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await longRunningTaskWrapper({
|
await longRunningTaskWrapper({
|
||||||
|
@ -1092,7 +1096,7 @@ function setDisappearingMessages(
|
||||||
return async dispatch => {
|
return async dispatch => {
|
||||||
const conversation = window.ConversationController.get(conversationId);
|
const conversation = window.ConversationController.get(conversationId);
|
||||||
if (!conversation) {
|
if (!conversation) {
|
||||||
throw new Error('No conversation found');
|
throw new Error('setDisappearingMessages: No conversation found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const valueToSet = seconds > 0 ? seconds : undefined;
|
const valueToSet = seconds > 0 ? seconds : undefined;
|
||||||
|
@ -1112,13 +1116,51 @@ function setDisappearingMessages(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setDontNotifyForMentionsIfMuted(
|
||||||
|
conversationId: string,
|
||||||
|
newValue: boolean
|
||||||
|
): NoopActionType {
|
||||||
|
const conversation = window.ConversationController.get(conversationId);
|
||||||
|
if (!conversation) {
|
||||||
|
throw new Error('setDontNotifyForMentionsIfMuted: No conversation found');
|
||||||
|
}
|
||||||
|
|
||||||
|
conversation.setDontNotifyForMentionsIfMuted(newValue);
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'NOOP',
|
||||||
|
payload: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function setMuteExpiration(
|
||||||
|
conversationId: string,
|
||||||
|
muteExpiresAt = 0
|
||||||
|
): NoopActionType {
|
||||||
|
const conversation = window.ConversationController.get(conversationId);
|
||||||
|
if (!conversation) {
|
||||||
|
throw new Error('setMuteExpiration: No conversation found');
|
||||||
|
}
|
||||||
|
|
||||||
|
conversation.setMuteExpiration(
|
||||||
|
muteExpiresAt >= Number.MAX_SAFE_INTEGER
|
||||||
|
? muteExpiresAt
|
||||||
|
: Date.now() + muteExpiresAt
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'NOOP',
|
||||||
|
payload: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function destroyMessages(
|
function destroyMessages(
|
||||||
conversationId: string
|
conversationId: string
|
||||||
): ThunkAction<void, RootStateType, unknown, NoopActionType> {
|
): ThunkAction<void, RootStateType, unknown, NoopActionType> {
|
||||||
return async dispatch => {
|
return async dispatch => {
|
||||||
const conversation = window.ConversationController.get(conversationId);
|
const conversation = window.ConversationController.get(conversationId);
|
||||||
if (!conversation) {
|
if (!conversation) {
|
||||||
throw new Error('No conversation found');
|
throw new Error('destroyMessages: No conversation found');
|
||||||
}
|
}
|
||||||
|
|
||||||
await longRunningTaskWrapper({
|
await longRunningTaskWrapper({
|
||||||
|
@ -1144,7 +1186,7 @@ function generateNewGroupLink(
|
||||||
return async dispatch => {
|
return async dispatch => {
|
||||||
const conversation = window.ConversationController.get(conversationId);
|
const conversation = window.ConversationController.get(conversationId);
|
||||||
if (!conversation) {
|
if (!conversation) {
|
||||||
throw new Error('No conversation found');
|
throw new Error('generateNewGroupLink: No conversation found');
|
||||||
}
|
}
|
||||||
|
|
||||||
await longRunningTaskWrapper({
|
await longRunningTaskWrapper({
|
||||||
|
@ -1217,7 +1259,9 @@ function setAccessControlAddFromInviteLinkSetting(
|
||||||
return async dispatch => {
|
return async dispatch => {
|
||||||
const conversation = window.ConversationController.get(conversationId);
|
const conversation = window.ConversationController.get(conversationId);
|
||||||
if (!conversation) {
|
if (!conversation) {
|
||||||
throw new Error('No conversation found');
|
throw new Error(
|
||||||
|
'setAccessControlAddFromInviteLinkSetting: No conversation found'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await longRunningTaskWrapper({
|
await longRunningTaskWrapper({
|
||||||
|
@ -1280,7 +1324,7 @@ function saveAvatarToDisk(
|
||||||
): ThunkAction<void, RootStateType, unknown, ReplaceAvatarsActionType> {
|
): ThunkAction<void, RootStateType, unknown, ReplaceAvatarsActionType> {
|
||||||
return async (dispatch, getState) => {
|
return async (dispatch, getState) => {
|
||||||
if (!avatarData.buffer) {
|
if (!avatarData.buffer) {
|
||||||
throw new Error('No avatar Uint8Array provided');
|
throw new Error('saveAvatarToDisk: No avatar Uint8Array provided');
|
||||||
}
|
}
|
||||||
|
|
||||||
strictAssert(conversationId, 'conversationId not provided');
|
strictAssert(conversationId, 'conversationId not provided');
|
||||||
|
|
|
@ -5,7 +5,13 @@ import { useBoundActions } from '../../hooks/useBoundActions';
|
||||||
import type { ReplacementValuesType } from '../../types/Util';
|
import type { ReplacementValuesType } from '../../types/Util';
|
||||||
|
|
||||||
export enum ToastType {
|
export enum ToastType {
|
||||||
|
AddingUserToGroup = 'AddingUserToGroup',
|
||||||
|
CannotStartGroupCall = 'CannotStartGroupCall',
|
||||||
|
CopiedUsername = 'CopiedUsername',
|
||||||
|
CopiedUsernameLink = 'CopiedUsernameLink',
|
||||||
|
DeleteForEveryoneFailed = 'DeleteForEveryoneFailed',
|
||||||
Error = 'Error',
|
Error = 'Error',
|
||||||
|
FailedToDeleteUsername = 'FailedToDeleteUsername',
|
||||||
MessageBodyTooLong = 'MessageBodyTooLong',
|
MessageBodyTooLong = 'MessageBodyTooLong',
|
||||||
StoryMuted = 'StoryMuted',
|
StoryMuted = 'StoryMuted',
|
||||||
StoryReact = 'StoryReact',
|
StoryReact = 'StoryReact',
|
||||||
|
@ -13,12 +19,7 @@ export enum ToastType {
|
||||||
StoryVideoError = 'StoryVideoError',
|
StoryVideoError = 'StoryVideoError',
|
||||||
StoryVideoTooLong = 'StoryVideoTooLong',
|
StoryVideoTooLong = 'StoryVideoTooLong',
|
||||||
StoryVideoUnsupported = 'StoryVideoUnsupported',
|
StoryVideoUnsupported = 'StoryVideoUnsupported',
|
||||||
AddingUserToGroup = 'AddingUserToGroup',
|
|
||||||
UserAddedToGroup = 'UserAddedToGroup',
|
UserAddedToGroup = 'UserAddedToGroup',
|
||||||
FailedToDeleteUsername = 'FailedToDeleteUsername',
|
|
||||||
CopiedUsername = 'CopiedUsername',
|
|
||||||
CopiedUsernameLink = 'CopiedUsernameLink',
|
|
||||||
DeleteForEveryoneFailed = 'DeleteForEveryoneFailed',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// State
|
// State
|
||||||
|
|
|
@ -57,9 +57,6 @@ export type SmartConversationDetailsProps = {
|
||||||
onBlock: () => void;
|
onBlock: () => void;
|
||||||
onLeave: () => void;
|
onLeave: () => void;
|
||||||
onUnblock: () => void;
|
onUnblock: () => void;
|
||||||
setMuteExpiration: (muteExpiresAt: undefined | number) => unknown;
|
|
||||||
onOutgoingAudioCallInConversation: () => unknown;
|
|
||||||
onOutgoingVideoCallInConversation: () => unknown;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const ACCESS_ENUM = Proto.AccessControl.AccessRequired;
|
const ACCESS_ENUM = Proto.AccessControl.AccessRequired;
|
||||||
|
|
|
@ -33,10 +33,7 @@ export type OwnProps = {
|
||||||
onGoBack: () => void;
|
onGoBack: () => void;
|
||||||
onMarkUnread: () => void;
|
onMarkUnread: () => void;
|
||||||
onMoveToInbox: () => void;
|
onMoveToInbox: () => void;
|
||||||
onOutgoingAudioCallInConversation: () => void;
|
|
||||||
onOutgoingVideoCallInConversation: () => void;
|
|
||||||
onSearchInConversation: () => void;
|
onSearchInConversation: () => void;
|
||||||
onSetMuteNotifications: (seconds: number) => void;
|
|
||||||
onSetPin: (value: boolean) => void;
|
onSetPin: (value: boolean) => void;
|
||||||
onShowAllMedia: () => void;
|
onShowAllMedia: () => void;
|
||||||
onShowConversationDetails: () => void;
|
onShowConversationDetails: () => void;
|
||||||
|
|
|
@ -7,36 +7,31 @@ import type { StateType } from '../reducer';
|
||||||
import { getIntl } from '../selectors/user';
|
import { getIntl } from '../selectors/user';
|
||||||
import { getConversationByIdSelector } from '../selectors/conversations';
|
import { getConversationByIdSelector } from '../selectors/conversations';
|
||||||
import { strictAssert } from '../../util/assert';
|
import { strictAssert } from '../../util/assert';
|
||||||
|
import { mapDispatchToProps } from '../actions';
|
||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
setDontNotifyForMentionsIfMuted: (
|
|
||||||
dontNotifyForMentionsIfMuted: boolean
|
|
||||||
) => unknown;
|
|
||||||
setMuteExpiration: (muteExpiresAt: undefined | number) => unknown;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state: StateType, props: OwnProps) => {
|
const mapStateToProps = (state: StateType, props: OwnProps) => {
|
||||||
const { conversationId, setDontNotifyForMentionsIfMuted, setMuteExpiration } =
|
const { conversationId } = props;
|
||||||
props;
|
|
||||||
|
|
||||||
const conversationSelector = getConversationByIdSelector(state);
|
const conversationSelector = getConversationByIdSelector(state);
|
||||||
const conversation = conversationSelector(conversationId);
|
const conversation = conversationSelector(conversationId);
|
||||||
strictAssert(conversation, 'Expected a conversation to be found');
|
strictAssert(conversation, 'Expected a conversation to be found');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: conversationId,
|
||||||
conversationType: conversation.type,
|
conversationType: conversation.type,
|
||||||
dontNotifyForMentionsIfMuted: Boolean(
|
dontNotifyForMentionsIfMuted: Boolean(
|
||||||
conversation.dontNotifyForMentionsIfMuted
|
conversation.dontNotifyForMentionsIfMuted
|
||||||
),
|
),
|
||||||
i18n: getIntl(state),
|
i18n: getIntl(state),
|
||||||
muteExpiresAt: conversation.muteExpiresAt,
|
muteExpiresAt: conversation.muteExpiresAt,
|
||||||
setDontNotifyForMentionsIfMuted,
|
|
||||||
setMuteExpiration,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const smart = connect(mapStateToProps, {});
|
const smart = connect(mapStateToProps, mapDispatchToProps);
|
||||||
|
|
||||||
export const SmartConversationNotificationsSettings = smart(
|
export const SmartConversationNotificationsSettings = smart(
|
||||||
ConversationNotificationsSettings
|
ConversationNotificationsSettings
|
||||||
|
|
38
ts/util/isCallSafe.ts
Normal file
38
ts/util/isCallSafe.ts
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import type { ConversationAttributesType } from '../model-types';
|
||||||
|
import type { RecipientsByConversation } from '../state/ducks/stories';
|
||||||
|
|
||||||
|
import * as log from '../logging/log';
|
||||||
|
import { SafetyNumberChangeSource } from '../components/SafetyNumberChangeDialog';
|
||||||
|
import { blockSendUntilConversationsAreVerified } from './blockSendUntilConversationsAreVerified';
|
||||||
|
import { getConversationMembers } from './getConversationMembers';
|
||||||
|
import { UUID } from '../types/UUID';
|
||||||
|
import { isNotNil } from './isNotNil';
|
||||||
|
|
||||||
|
export async function isCallSafe(
|
||||||
|
attributes: ConversationAttributesType
|
||||||
|
): Promise<boolean> {
|
||||||
|
const recipientsByConversation: RecipientsByConversation = {
|
||||||
|
[attributes.id]: {
|
||||||
|
uuids: getConversationMembers(attributes)
|
||||||
|
.map(member =>
|
||||||
|
member.uuid ? UUID.checkedLookup(member.uuid).toString() : undefined
|
||||||
|
)
|
||||||
|
.filter(isNotNil),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const callAnyway = await blockSendUntilConversationsAreVerified(
|
||||||
|
recipientsByConversation,
|
||||||
|
SafetyNumberChangeSource.Calling
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!callAnyway) {
|
||||||
|
log.info('Safety number change dialog not accepted, new call not allowed.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
|
@ -13,7 +13,6 @@ import type {
|
||||||
ToastCannotOpenGiftBadge,
|
ToastCannotOpenGiftBadge,
|
||||||
ToastPropsType as ToastCannotOpenGiftBadgePropsType,
|
ToastPropsType as ToastCannotOpenGiftBadgePropsType,
|
||||||
} from '../components/ToastCannotOpenGiftBadge';
|
} from '../components/ToastCannotOpenGiftBadge';
|
||||||
import type { ToastCannotStartGroupCall } from '../components/ToastCannotStartGroupCall';
|
|
||||||
import type { ToastCaptchaFailed } from '../components/ToastCaptchaFailed';
|
import type { ToastCaptchaFailed } from '../components/ToastCaptchaFailed';
|
||||||
import type { ToastCaptchaSolved } from '../components/ToastCaptchaSolved';
|
import type { ToastCaptchaSolved } from '../components/ToastCaptchaSolved';
|
||||||
import type {
|
import type {
|
||||||
|
@ -60,7 +59,6 @@ export function showToast(Toast: typeof ToastAlreadyRequestedToJoin): void;
|
||||||
export function showToast(Toast: typeof ToastBlocked): void;
|
export function showToast(Toast: typeof ToastBlocked): void;
|
||||||
export function showToast(Toast: typeof ToastBlockedGroup): void;
|
export function showToast(Toast: typeof ToastBlockedGroup): void;
|
||||||
export function showToast(Toast: typeof ToastUnsupportedMultiAttachment): void;
|
export function showToast(Toast: typeof ToastUnsupportedMultiAttachment): void;
|
||||||
export function showToast(Toast: typeof ToastCannotStartGroupCall): void;
|
|
||||||
export function showToast(
|
export function showToast(
|
||||||
Toast: typeof ToastCannotOpenGiftBadge,
|
Toast: typeof ToastCannotOpenGiftBadge,
|
||||||
props: Omit<ToastCannotOpenGiftBadgePropsType, 'i18n' | 'onClose'>
|
props: Omit<ToastCannotOpenGiftBadgePropsType, 'i18n' | 'onClose'>
|
||||||
|
|
|
@ -61,7 +61,6 @@ import { SignalService as Proto } from '../protobuf';
|
||||||
import { ToastBlocked } from '../components/ToastBlocked';
|
import { ToastBlocked } from '../components/ToastBlocked';
|
||||||
import { ToastBlockedGroup } from '../components/ToastBlockedGroup';
|
import { ToastBlockedGroup } from '../components/ToastBlockedGroup';
|
||||||
import { ToastCannotMixMultiAndNonMultiAttachments } from '../components/ToastCannotMixMultiAndNonMultiAttachments';
|
import { ToastCannotMixMultiAndNonMultiAttachments } from '../components/ToastCannotMixMultiAndNonMultiAttachments';
|
||||||
import { ToastCannotStartGroupCall } from '../components/ToastCannotStartGroupCall';
|
|
||||||
import { ToastConversationArchived } from '../components/ToastConversationArchived';
|
import { ToastConversationArchived } from '../components/ToastConversationArchived';
|
||||||
import { ToastConversationMarkedUnread } from '../components/ToastConversationMarkedUnread';
|
import { ToastConversationMarkedUnread } from '../components/ToastConversationMarkedUnread';
|
||||||
import { ToastConversationUnarchived } from '../components/ToastConversationUnarchived';
|
import { ToastConversationUnarchived } from '../components/ToastConversationUnarchived';
|
||||||
|
@ -108,10 +107,8 @@ import { saveAttachment } from '../util/saveAttachment';
|
||||||
import { SECOND } from '../util/durations';
|
import { SECOND } from '../util/durations';
|
||||||
import { blockSendUntilConversationsAreVerified } from '../util/blockSendUntilConversationsAreVerified';
|
import { blockSendUntilConversationsAreVerified } from '../util/blockSendUntilConversationsAreVerified';
|
||||||
import { SafetyNumberChangeSource } from '../components/SafetyNumberChangeDialog';
|
import { SafetyNumberChangeSource } from '../components/SafetyNumberChangeDialog';
|
||||||
import { getOwn } from '../util/getOwn';
|
|
||||||
import { CallMode } from '../types/Calling';
|
|
||||||
import { isAnybodyElseInGroupCall } from '../state/ducks/calling';
|
|
||||||
import { startConversation } from '../util/startConversation';
|
import { startConversation } from '../util/startConversation';
|
||||||
|
import { longRunningTaskWrapper } from '../util/longRunningTaskWrapper';
|
||||||
|
|
||||||
type AttachmentOptions = {
|
type AttachmentOptions = {
|
||||||
messageId: string;
|
messageId: string;
|
||||||
|
@ -306,12 +303,6 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setMuteExpiration(ms = 0): void {
|
|
||||||
this.model.setMuteExpiration(
|
|
||||||
ms >= Number.MAX_SAFE_INTEGER ? ms : Date.now() + ms
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
setPin(value: boolean): void {
|
setPin(value: boolean): void {
|
||||||
if (value) {
|
if (value) {
|
||||||
const pinnedConversationIds = window.storage.get(
|
const pinnedConversationIds = window.storage.get(
|
||||||
|
@ -338,15 +329,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
const { searchInConversation } = window.reduxActions.search;
|
const { searchInConversation } = window.reduxActions.search;
|
||||||
searchInConversation(this.model.id);
|
searchInConversation(this.model.id);
|
||||||
},
|
},
|
||||||
onSetMuteNotifications: this.setMuteExpiration.bind(this),
|
|
||||||
onSetPin: this.setPin.bind(this),
|
onSetPin: this.setPin.bind(this),
|
||||||
// These are view only and don't update the Conversation model, so they
|
|
||||||
// need a manual update call.
|
|
||||||
onOutgoingAudioCallInConversation:
|
|
||||||
this.onOutgoingAudioCallInConversation.bind(this),
|
|
||||||
onOutgoingVideoCallInConversation:
|
|
||||||
this.onOutgoingVideoCallInConversation.bind(this),
|
|
||||||
|
|
||||||
onShowConversationDetails: () => {
|
onShowConversationDetails: () => {
|
||||||
this.showConversationDetails();
|
this.showConversationDetails();
|
||||||
},
|
},
|
||||||
|
@ -505,7 +488,8 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
messageRequestEnum.ACCEPT
|
messageRequestEnum.ACCEPT
|
||||||
),
|
),
|
||||||
removeMember: (conversationId: string) => {
|
removeMember: (conversationId: string) => {
|
||||||
this.longRunningTaskWrapper({
|
longRunningTaskWrapper({
|
||||||
|
idForLogging: this.model.idForLogging(),
|
||||||
name: 'removeMember',
|
name: 'removeMember',
|
||||||
task: () => this.model.removeFromGroupV2(conversationId),
|
task: () => this.model.removeFromGroupV2(conversationId),
|
||||||
});
|
});
|
||||||
|
@ -575,7 +559,8 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
okText: window.i18n('GroupV2--join--cancel-request-to-join--yes'),
|
okText: window.i18n('GroupV2--join--cancel-request-to-join--yes'),
|
||||||
cancelText: window.i18n('GroupV2--join--cancel-request-to-join--no'),
|
cancelText: window.i18n('GroupV2--join--cancel-request-to-join--no'),
|
||||||
resolve: () => {
|
resolve: () => {
|
||||||
this.longRunningTaskWrapper({
|
longRunningTaskWrapper({
|
||||||
|
idForLogging: this.model.idForLogging(),
|
||||||
name: 'onCancelJoinRequest',
|
name: 'onCancelJoinRequest',
|
||||||
task: async () => this.model.cancelJoinRequest(),
|
task: async () => this.model.cancelJoinRequest(),
|
||||||
});
|
});
|
||||||
|
@ -630,83 +615,6 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
this.$('.ConversationView__template').append(this.conversationView.el);
|
this.$('.ConversationView__template').append(this.conversationView.el);
|
||||||
}
|
}
|
||||||
|
|
||||||
async onOutgoingVideoCallInConversation(): Promise<void> {
|
|
||||||
log.info('onOutgoingVideoCallInConversation: about to start a video call');
|
|
||||||
|
|
||||||
// if it's a group call on an announcementsOnly group
|
|
||||||
// only allow join if the call has already been started (presumably by the admin)
|
|
||||||
if (this.model.get('announcementsOnly') && !this.model.areWeAdmin()) {
|
|
||||||
const call = getOwn(
|
|
||||||
window.reduxStore.getState().calling.callsByConversation,
|
|
||||||
this.model.id
|
|
||||||
);
|
|
||||||
|
|
||||||
// technically not necessary, but isAnybodyElseInGroupCall requires it
|
|
||||||
const ourUuid = window.storage.user.getCheckedUuid().toString();
|
|
||||||
|
|
||||||
const isOngoingGroupCall =
|
|
||||||
call &&
|
|
||||||
ourUuid &&
|
|
||||||
call.callMode === CallMode.Group &&
|
|
||||||
call.peekInfo &&
|
|
||||||
isAnybodyElseInGroupCall(call.peekInfo, ourUuid);
|
|
||||||
|
|
||||||
if (!isOngoingGroupCall) {
|
|
||||||
showToast(ToastCannotStartGroupCall);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (await this.isCallSafe()) {
|
|
||||||
log.info(
|
|
||||||
'onOutgoingVideoCallInConversation: call is deemed "safe". Making call'
|
|
||||||
);
|
|
||||||
window.reduxActions.calling.startCallingLobby({
|
|
||||||
conversationId: this.model.id,
|
|
||||||
isVideoCall: true,
|
|
||||||
});
|
|
||||||
log.info('onOutgoingVideoCallInConversation: started the call');
|
|
||||||
} else {
|
|
||||||
log.info(
|
|
||||||
'onOutgoingVideoCallInConversation: call is deemed "unsafe". Stopping'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async onOutgoingAudioCallInConversation(): Promise<void> {
|
|
||||||
log.info('onOutgoingAudioCallInConversation: about to start an audio call');
|
|
||||||
|
|
||||||
if (await this.isCallSafe()) {
|
|
||||||
log.info(
|
|
||||||
'onOutgoingAudioCallInConversation: call is deemed "safe". Making call'
|
|
||||||
);
|
|
||||||
window.reduxActions.calling.startCallingLobby({
|
|
||||||
conversationId: this.model.id,
|
|
||||||
isVideoCall: false,
|
|
||||||
});
|
|
||||||
log.info('onOutgoingAudioCallInConversation: started the call');
|
|
||||||
} else {
|
|
||||||
log.info(
|
|
||||||
'onOutgoingAudioCallInConversation: call is deemed "unsafe". Stopping'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async longRunningTaskWrapper<T>({
|
|
||||||
name,
|
|
||||||
task,
|
|
||||||
}: {
|
|
||||||
name: string;
|
|
||||||
task: () => Promise<T>;
|
|
||||||
}): Promise<T> {
|
|
||||||
const idForLogging = this.model.idForLogging();
|
|
||||||
return window.Signal.Util.longRunningTaskWrapper({
|
|
||||||
name,
|
|
||||||
idForLogging,
|
|
||||||
task,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getMessageActions(): MessageActionsType {
|
getMessageActions(): MessageActionsType {
|
||||||
const reactToMessage = async (
|
const reactToMessage = async (
|
||||||
messageId: string,
|
messageId: string,
|
||||||
|
@ -896,7 +804,8 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
const migrate = () => {
|
const migrate = () => {
|
||||||
onClose();
|
onClose();
|
||||||
|
|
||||||
this.longRunningTaskWrapper({
|
longRunningTaskWrapper({
|
||||||
|
idForLogging: this.model.idForLogging(),
|
||||||
name: 'initiateMigrationToGroupV2',
|
name: 'initiateMigrationToGroupV2',
|
||||||
task: () => window.Signal.Groups.initiateMigrationToGroupV2(this.model),
|
task: () => window.Signal.Groups.initiateMigrationToGroupV2(this.model),
|
||||||
});
|
});
|
||||||
|
@ -905,7 +814,8 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
// Note: this call will throw if, after generating member lists, we are no longer a
|
// Note: this call will throw if, after generating member lists, we are no longer a
|
||||||
// member or are in the pending member list.
|
// member or are in the pending member list.
|
||||||
const { droppedGV2MemberIds, pendingMembersV2 } =
|
const { droppedGV2MemberIds, pendingMembersV2 } =
|
||||||
await this.longRunningTaskWrapper({
|
await longRunningTaskWrapper({
|
||||||
|
idForLogging: this.model.idForLogging(),
|
||||||
name: 'getGroupMigrationMembers',
|
name: 'getGroupMigrationMembers',
|
||||||
task: () => window.Signal.Groups.getGroupMigrationMembers(this.model),
|
task: () => window.Signal.Groups.getGroupMigrationMembers(this.model),
|
||||||
});
|
});
|
||||||
|
@ -1103,7 +1013,8 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
model: ConversationModel,
|
model: ConversationModel,
|
||||||
messageRequestType: number
|
messageRequestType: number
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
return this.longRunningTaskWrapper({
|
return longRunningTaskWrapper({
|
||||||
|
idForLogging: this.model.idForLogging(),
|
||||||
name,
|
name,
|
||||||
task: model.syncMessageRequestResponse.bind(model, messageRequestType),
|
task: model.syncMessageRequestResponse.bind(model, messageRequestType),
|
||||||
});
|
});
|
||||||
|
@ -1112,7 +1023,8 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
blockAndReportSpam(model: ConversationModel): Promise<void> {
|
blockAndReportSpam(model: ConversationModel): Promise<void> {
|
||||||
const messageRequestEnum = Proto.SyncMessage.MessageRequestResponse.Type;
|
const messageRequestEnum = Proto.SyncMessage.MessageRequestResponse.Type;
|
||||||
|
|
||||||
return this.longRunningTaskWrapper({
|
return longRunningTaskWrapper({
|
||||||
|
idForLogging: this.model.idForLogging(),
|
||||||
name: 'blockAndReportSpam',
|
name: 'blockAndReportSpam',
|
||||||
task: async () => {
|
task: async () => {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
|
@ -1862,9 +1774,6 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
window.reduxStore,
|
window.reduxStore,
|
||||||
{
|
{
|
||||||
conversationId: this.model.id,
|
conversationId: this.model.id,
|
||||||
setDontNotifyForMentionsIfMuted:
|
|
||||||
this.model.setDontNotifyForMentionsIfMuted.bind(this.model),
|
|
||||||
setMuteExpiration: this.setMuteExpiration.bind(this),
|
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
@ -1900,7 +1809,8 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
// dried up and hoisted to methods on ConversationView
|
// dried up and hoisted to methods on ConversationView
|
||||||
|
|
||||||
const onLeave = () => {
|
const onLeave = () => {
|
||||||
this.longRunningTaskWrapper({
|
longRunningTaskWrapper({
|
||||||
|
idForLogging: this.model.idForLogging(),
|
||||||
name: 'onLeave',
|
name: 'onLeave',
|
||||||
task: () => this.model.leaveGroupV2(),
|
task: () => this.model.leaveGroupV2(),
|
||||||
});
|
});
|
||||||
|
@ -1938,11 +1848,6 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
messageRequestEnum.ACCEPT
|
messageRequestEnum.ACCEPT
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
setMuteExpiration: this.setMuteExpiration.bind(this),
|
|
||||||
onOutgoingAudioCallInConversation:
|
|
||||||
this.onOutgoingAudioCallInConversation.bind(this),
|
|
||||||
onOutgoingVideoCallInConversation:
|
|
||||||
this.onOutgoingVideoCallInConversation.bind(this),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const view = new ReactWrapperView({
|
const view = new ReactWrapperView({
|
||||||
|
@ -2128,28 +2033,6 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async isCallSafe(): Promise<boolean> {
|
|
||||||
const recipientsByConversation = {
|
|
||||||
[this.model.id]: {
|
|
||||||
uuids: this.model.getMemberUuids().map(uuid => uuid.toString()),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const callAnyway = await blockSendUntilConversationsAreVerified(
|
|
||||||
recipientsByConversation,
|
|
||||||
SafetyNumberChangeSource.Calling
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!callAnyway) {
|
|
||||||
log.info(
|
|
||||||
'Safety number change dialog not accepted, new call not allowed.'
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendStickerMessage(options: {
|
async sendStickerMessage(options: {
|
||||||
packId: string;
|
packId: string;
|
||||||
stickerId: number;
|
stickerId: number;
|
||||||
|
|
Loading…
Add table
Reference in a new issue