Refactor smart components
Co-authored-by: Fedor Indutny <79877362+indutny-signal@users.noreply.github.com>
This commit is contained in:
parent
05c09ef769
commit
27b55e472d
109 changed files with 3583 additions and 2629 deletions
|
@ -1,7 +1,7 @@
|
||||||
// Copyright 2022 Signal Messenger, LLC
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React, { useContext } from 'react';
|
import React from 'react';
|
||||||
import type { Meta, StoryFn } from '@storybook/react';
|
import type { Meta, StoryFn } from '@storybook/react';
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from '@storybook/addon-actions';
|
||||||
|
|
||||||
|
@ -13,7 +13,6 @@ import {
|
||||||
} from '../test-both/helpers/getDefaultConversation';
|
} from '../test-both/helpers/getDefaultConversation';
|
||||||
import { setupI18n } from '../util/setupI18n';
|
import { setupI18n } from '../util/setupI18n';
|
||||||
import { AddUserToAnotherGroupModal } from './AddUserToAnotherGroupModal';
|
import { AddUserToAnotherGroupModal } from './AddUserToAnotherGroupModal';
|
||||||
import { StorybookThemeContext } from '../../.storybook/StorybookThemeContext';
|
|
||||||
|
|
||||||
const i18n = setupI18n('en', enMessages);
|
const i18n = setupI18n('en', enMessages);
|
||||||
|
|
||||||
|
@ -36,7 +35,6 @@ const Template: StoryFn<Props> = args => {
|
||||||
toggleAddUserToAnotherGroupModal={action(
|
toggleAddUserToAnotherGroupModal={action(
|
||||||
'toggleAddUserToAnotherGroupModal'
|
'toggleAddUserToAnotherGroupModal'
|
||||||
)}
|
)}
|
||||||
theme={useContext(StorybookThemeContext)}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,7 +6,7 @@ import React, { useCallback } from 'react';
|
||||||
import type { ListRowProps } from 'react-virtualized';
|
import type { ListRowProps } from 'react-virtualized';
|
||||||
|
|
||||||
import type { ConversationType } from '../state/ducks/conversations';
|
import type { ConversationType } from '../state/ducks/conversations';
|
||||||
import type { LocalizerType, ThemeType } from '../types/Util';
|
import type { LocalizerType } from '../types/Util';
|
||||||
import { ToastType } from '../types/Toast';
|
import { ToastType } from '../types/Toast';
|
||||||
import { filterAndSortConversationsByRecent } from '../util/filterAndSortConversations';
|
import { filterAndSortConversationsByRecent } from '../util/filterAndSortConversations';
|
||||||
import { ConfirmationDialog } from './ConfirmationDialog';
|
import { ConfirmationDialog } from './ConfirmationDialog';
|
||||||
|
@ -25,7 +25,6 @@ import { SizeObserver } from '../hooks/useSizeObserver';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
theme: ThemeType;
|
|
||||||
contact: Pick<ConversationType, 'id' | 'title' | 'serviceId' | 'pni'>;
|
contact: Pick<ConversationType, 'id' | 'title' | 'serviceId' | 'pni'>;
|
||||||
candidateConversations: ReadonlyArray<ConversationType>;
|
candidateConversations: ReadonlyArray<ConversationType>;
|
||||||
regionCode: string | undefined;
|
regionCode: string | undefined;
|
||||||
|
|
|
@ -139,9 +139,23 @@ export type PropsType = {
|
||||||
pauseVoiceNotePlayer: () => void;
|
pauseVoiceNotePlayer: () => void;
|
||||||
} & Pick<ReactionPickerProps, 'renderEmojiPicker'>;
|
} & Pick<ReactionPickerProps, 'renderEmojiPicker'>;
|
||||||
|
|
||||||
type ActiveCallManagerPropsType = PropsType & {
|
type ActiveCallManagerPropsType = {
|
||||||
activeCall: ActiveCallType;
|
activeCall: ActiveCallType;
|
||||||
};
|
} & Omit<
|
||||||
|
PropsType,
|
||||||
|
| 'acceptCall'
|
||||||
|
| 'bounceAppIconStart'
|
||||||
|
| 'bounceAppIconStop'
|
||||||
|
| 'declineCall'
|
||||||
|
| 'hasInitialLoadCompleted'
|
||||||
|
| 'incomingCall'
|
||||||
|
| 'isConversationTooBigToRin'
|
||||||
|
| 'notifyForCall'
|
||||||
|
| 'playRingtone'
|
||||||
|
| 'setIsCallActive'
|
||||||
|
| 'stopRingtone'
|
||||||
|
| 'isConversationTooBigToRing'
|
||||||
|
>;
|
||||||
|
|
||||||
function ActiveCallManager({
|
function ActiveCallManager({
|
||||||
activeCall,
|
activeCall,
|
||||||
|
@ -472,28 +486,69 @@ function ActiveCallManager({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CallManager(props: PropsType): JSX.Element | null {
|
export function CallManager({
|
||||||
const {
|
|
||||||
acceptCall,
|
acceptCall,
|
||||||
activeCall,
|
activeCall,
|
||||||
|
availableCameras,
|
||||||
bounceAppIconStart,
|
bounceAppIconStart,
|
||||||
bounceAppIconStop,
|
bounceAppIconStop,
|
||||||
|
callLink,
|
||||||
|
cancelCall,
|
||||||
|
changeCallView,
|
||||||
|
closeNeedPermissionScreen,
|
||||||
declineCall,
|
declineCall,
|
||||||
|
getGroupCallVideoFrameSource,
|
||||||
|
getPreferredBadge,
|
||||||
|
getPresentingSources,
|
||||||
|
hangUpActiveCall,
|
||||||
|
hasInitialLoadCompleted,
|
||||||
i18n,
|
i18n,
|
||||||
incomingCall,
|
incomingCall,
|
||||||
|
isConversationTooBigToRing,
|
||||||
|
isGroupCallRaiseHandEnabled,
|
||||||
|
isGroupCallReactionsEnabled,
|
||||||
|
keyChangeOk,
|
||||||
|
me,
|
||||||
notifyForCall,
|
notifyForCall,
|
||||||
|
openSystemPreferencesAction,
|
||||||
|
pauseVoiceNotePlayer,
|
||||||
playRingtone,
|
playRingtone,
|
||||||
stopRingtone,
|
renderDeviceSelection,
|
||||||
|
renderEmojiPicker,
|
||||||
|
renderReactionPicker,
|
||||||
|
renderSafetyNumberViewer,
|
||||||
|
sendGroupCallRaiseHand,
|
||||||
|
sendGroupCallReaction,
|
||||||
|
setGroupCallVideoRequest,
|
||||||
setIsCallActive,
|
setIsCallActive,
|
||||||
|
setLocalAudio,
|
||||||
|
setLocalPreview,
|
||||||
|
setLocalVideo,
|
||||||
setOutgoingRing,
|
setOutgoingRing,
|
||||||
} = props;
|
setPresenting,
|
||||||
|
setRendererCanvas,
|
||||||
|
showToast,
|
||||||
|
startCall,
|
||||||
|
stopRingtone,
|
||||||
|
switchFromPresentationView,
|
||||||
|
switchToPresentationView,
|
||||||
|
theme,
|
||||||
|
toggleParticipants,
|
||||||
|
togglePip,
|
||||||
|
toggleScreenRecordingPermissionsDialog,
|
||||||
|
toggleSettings,
|
||||||
|
}: PropsType): JSX.Element | null {
|
||||||
const isCallActive = Boolean(activeCall);
|
const isCallActive = Boolean(activeCall);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsCallActive(isCallActive);
|
setIsCallActive(isCallActive);
|
||||||
}, [isCallActive, setIsCallActive]);
|
}, [isCallActive, setIsCallActive]);
|
||||||
|
|
||||||
const shouldRing = getShouldRing(props);
|
const shouldRing = getShouldRing({
|
||||||
|
activeCall,
|
||||||
|
incomingCall,
|
||||||
|
isConversationTooBigToRing,
|
||||||
|
hasInitialLoadCompleted,
|
||||||
|
});
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (shouldRing) {
|
if (shouldRing) {
|
||||||
log.info('CallManager: Playing ringtone');
|
log.info('CallManager: Playing ringtone');
|
||||||
|
@ -529,8 +584,50 @@ export function CallManager(props: PropsType): JSX.Element | null {
|
||||||
// `props` should logically have an `activeCall` at this point, but TypeScript can't
|
// `props` should logically have an `activeCall` at this point, but TypeScript can't
|
||||||
// figure that out, so we pass it in again.
|
// figure that out, so we pass it in again.
|
||||||
return (
|
return (
|
||||||
<CallingToastProvider i18n={props.i18n}>
|
<CallingToastProvider i18n={i18n}>
|
||||||
<ActiveCallManager {...props} activeCall={activeCall} />
|
<ActiveCallManager
|
||||||
|
activeCall={activeCall}
|
||||||
|
availableCameras={availableCameras}
|
||||||
|
callLink={callLink}
|
||||||
|
cancelCall={cancelCall}
|
||||||
|
changeCallView={changeCallView}
|
||||||
|
closeNeedPermissionScreen={closeNeedPermissionScreen}
|
||||||
|
getGroupCallVideoFrameSource={getGroupCallVideoFrameSource}
|
||||||
|
getPreferredBadge={getPreferredBadge}
|
||||||
|
getPresentingSources={getPresentingSources}
|
||||||
|
hangUpActiveCall={hangUpActiveCall}
|
||||||
|
i18n={i18n}
|
||||||
|
isGroupCallRaiseHandEnabled={isGroupCallRaiseHandEnabled}
|
||||||
|
isGroupCallReactionsEnabled={isGroupCallReactionsEnabled}
|
||||||
|
keyChangeOk={keyChangeOk}
|
||||||
|
me={me}
|
||||||
|
openSystemPreferencesAction={openSystemPreferencesAction}
|
||||||
|
pauseVoiceNotePlayer={pauseVoiceNotePlayer}
|
||||||
|
renderDeviceSelection={renderDeviceSelection}
|
||||||
|
renderEmojiPicker={renderEmojiPicker}
|
||||||
|
renderReactionPicker={renderReactionPicker}
|
||||||
|
renderSafetyNumberViewer={renderSafetyNumberViewer}
|
||||||
|
sendGroupCallRaiseHand={sendGroupCallRaiseHand}
|
||||||
|
sendGroupCallReaction={sendGroupCallReaction}
|
||||||
|
setGroupCallVideoRequest={setGroupCallVideoRequest}
|
||||||
|
setLocalAudio={setLocalAudio}
|
||||||
|
setLocalPreview={setLocalPreview}
|
||||||
|
setLocalVideo={setLocalVideo}
|
||||||
|
setOutgoingRing={setOutgoingRing}
|
||||||
|
setPresenting={setPresenting}
|
||||||
|
setRendererCanvas={setRendererCanvas}
|
||||||
|
showToast={showToast}
|
||||||
|
startCall={startCall}
|
||||||
|
switchFromPresentationView={switchFromPresentationView}
|
||||||
|
switchToPresentationView={switchToPresentationView}
|
||||||
|
theme={theme}
|
||||||
|
toggleParticipants={toggleParticipants}
|
||||||
|
togglePip={togglePip}
|
||||||
|
toggleScreenRecordingPermissionsDialog={
|
||||||
|
toggleScreenRecordingPermissionsDialog
|
||||||
|
}
|
||||||
|
toggleSettings={toggleSettings}
|
||||||
|
/>
|
||||||
</CallingToastProvider>
|
</CallingToastProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,17 +8,20 @@ import { Button, ButtonVariant } from './Button';
|
||||||
import { Modal } from './Modal';
|
import { Modal } from './Modal';
|
||||||
import { Spinner } from './Spinner';
|
import { Spinner } from './Spinner';
|
||||||
|
|
||||||
export type PropsType = {
|
export type PropsType = Readonly<{
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
isPending: boolean;
|
isPending: boolean;
|
||||||
|
|
||||||
onContinue: () => void;
|
onContinue: () => void;
|
||||||
onSkip: () => void;
|
onSkip: () => void;
|
||||||
};
|
}>;
|
||||||
|
|
||||||
export function CaptchaDialog(props: Readonly<PropsType>): JSX.Element {
|
|
||||||
const { i18n, isPending, onSkip, onContinue } = props;
|
|
||||||
|
|
||||||
|
export function CaptchaDialog({
|
||||||
|
i18n,
|
||||||
|
isPending,
|
||||||
|
onSkip,
|
||||||
|
onContinue,
|
||||||
|
}: PropsType): JSX.Element {
|
||||||
const [isClosing, setIsClosing] = useState(false);
|
const [isClosing, setIsClosing] = useState(false);
|
||||||
|
|
||||||
const buttonRef = useRef<HTMLButtonElement | null>(null);
|
const buttonRef = useRef<HTMLButtonElement | null>(null);
|
||||||
|
|
|
@ -18,9 +18,12 @@ export type PropsType = {
|
||||||
isPending: boolean;
|
isPending: boolean;
|
||||||
} & PropsActionsType;
|
} & PropsActionsType;
|
||||||
|
|
||||||
export function CrashReportDialog(props: Readonly<PropsType>): JSX.Element {
|
export function CrashReportDialog({
|
||||||
const { i18n, isPending, writeCrashReportsToLog, eraseCrashReports } = props;
|
i18n,
|
||||||
|
isPending,
|
||||||
|
writeCrashReportsToLog,
|
||||||
|
eraseCrashReports,
|
||||||
|
}: Readonly<PropsType>): JSX.Element {
|
||||||
const onEraseClick = (event: React.MouseEvent) => {
|
const onEraseClick = (event: React.MouseEvent) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import type { ComponentType } from 'react';
|
||||||
import React, {
|
import React, {
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
|
@ -61,9 +62,7 @@ export type DataPropsType = {
|
||||||
caretLocation?: number
|
caretLocation?: number
|
||||||
) => unknown;
|
) => unknown;
|
||||||
regionCode: string | undefined;
|
regionCode: string | undefined;
|
||||||
RenderCompositionTextArea: (
|
RenderCompositionTextArea: ComponentType<SmartCompositionTextAreaProps>;
|
||||||
props: SmartCompositionTextAreaProps
|
|
||||||
) => JSX.Element;
|
|
||||||
showToast: ShowToastAction;
|
showToast: ShowToastAction;
|
||||||
theme: ThemeType;
|
theme: ThemeType;
|
||||||
};
|
};
|
||||||
|
@ -413,9 +412,7 @@ type ForwardMessageEditorProps = Readonly<{
|
||||||
draft: MessageForwardDraft;
|
draft: MessageForwardDraft;
|
||||||
linkPreview: LinkPreviewType | null | void;
|
linkPreview: LinkPreviewType | null | void;
|
||||||
removeLinkPreview(): void;
|
removeLinkPreview(): void;
|
||||||
RenderCompositionTextArea: (
|
RenderCompositionTextArea: ComponentType<SmartCompositionTextAreaProps>;
|
||||||
props: SmartCompositionTextAreaProps
|
|
||||||
) => JSX.Element;
|
|
||||||
onChange: (
|
onChange: (
|
||||||
messageText: string,
|
messageText: string,
|
||||||
bodyRanges: HydratedBodyRangesType,
|
bodyRanges: HydratedBodyRangesType,
|
||||||
|
|
|
@ -36,13 +36,12 @@ const contact3: ConversationType = getDefaultConversation({
|
||||||
|
|
||||||
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
||||||
areWeInvited: Boolean(overrideProps.areWeInvited),
|
areWeInvited: Boolean(overrideProps.areWeInvited),
|
||||||
conversationId: '123',
|
|
||||||
droppedMembers: overrideProps.droppedMembers || [contact3, contact1],
|
droppedMembers: overrideProps.droppedMembers || [contact3, contact1],
|
||||||
getPreferredBadge: () => undefined,
|
getPreferredBadge: () => undefined,
|
||||||
hasMigrated: Boolean(overrideProps.hasMigrated),
|
hasMigrated: Boolean(overrideProps.hasMigrated),
|
||||||
i18n,
|
i18n,
|
||||||
invitedMembers: overrideProps.invitedMembers || [contact2],
|
invitedMembers: overrideProps.invitedMembers || [contact2],
|
||||||
migrate: action('migrate'),
|
onMigrate: action('onMigrate'),
|
||||||
onClose: action('onClose'),
|
onClose: action('onClose'),
|
||||||
theme: ThemeType.light,
|
theme: ThemeType.light,
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,7 +10,6 @@ import { sortByTitle } from '../util/sortByTitle';
|
||||||
import { missingCaseError } from '../util/missingCaseError';
|
import { missingCaseError } from '../util/missingCaseError';
|
||||||
|
|
||||||
export type DataPropsType = {
|
export type DataPropsType = {
|
||||||
conversationId: string;
|
|
||||||
readonly areWeInvited: boolean;
|
readonly areWeInvited: boolean;
|
||||||
readonly droppedMembers: Array<ConversationType>;
|
readonly droppedMembers: Array<ConversationType>;
|
||||||
readonly hasMigrated: boolean;
|
readonly hasMigrated: boolean;
|
||||||
|
@ -20,51 +19,25 @@ export type DataPropsType = {
|
||||||
readonly theme: ThemeType;
|
readonly theme: ThemeType;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ActionsPropsType =
|
type ActionsPropsType = Readonly<{
|
||||||
| {
|
onMigrate: () => unknown;
|
||||||
initiateMigrationToGroupV2: (conversationId: string) => unknown;
|
onClose: () => unknown;
|
||||||
closeGV2MigrationDialog: () => unknown;
|
}>;
|
||||||
}
|
|
||||||
| {
|
|
||||||
readonly migrate: () => unknown;
|
|
||||||
readonly onClose: () => unknown;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type PropsType = DataPropsType & ActionsPropsType;
|
export type PropsType = DataPropsType & ActionsPropsType;
|
||||||
|
|
||||||
export const GroupV1MigrationDialog: React.FunctionComponent<PropsType> =
|
export const GroupV1MigrationDialog: React.FunctionComponent<PropsType> =
|
||||||
React.memo(function GroupV1MigrationDialogInner(props: PropsType) {
|
React.memo(function GroupV1MigrationDialogInner({
|
||||||
const {
|
|
||||||
areWeInvited,
|
areWeInvited,
|
||||||
conversationId,
|
|
||||||
droppedMembers,
|
droppedMembers,
|
||||||
getPreferredBadge,
|
getPreferredBadge,
|
||||||
hasMigrated,
|
hasMigrated,
|
||||||
i18n,
|
i18n,
|
||||||
invitedMembers,
|
invitedMembers,
|
||||||
theme,
|
theme,
|
||||||
} = props;
|
onClose,
|
||||||
|
onMigrate,
|
||||||
let migrateHandler;
|
}: PropsType) {
|
||||||
if ('migrate' in props) {
|
|
||||||
migrateHandler = props.migrate;
|
|
||||||
} else if ('initiateMigrationToGroupV2' in props) {
|
|
||||||
migrateHandler = () => props.initiateMigrationToGroupV2(conversationId);
|
|
||||||
} else {
|
|
||||||
throw new Error(
|
|
||||||
'GroupV1MigrationDialog: No conversationId or migration function'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let closeHandler;
|
|
||||||
if ('onClose' in props) {
|
|
||||||
closeHandler = props.onClose;
|
|
||||||
} else if ('closeGV2MigrationDialog' in props) {
|
|
||||||
closeHandler = props.closeGV2MigrationDialog;
|
|
||||||
} else {
|
|
||||||
throw new Error('GroupV1MigrationDialog: No close function provided');
|
|
||||||
}
|
|
||||||
|
|
||||||
const title = hasMigrated
|
const title = hasMigrated
|
||||||
? i18n('icu:GroupV1--Migration--info--title')
|
? i18n('icu:GroupV1--Migration--info--title')
|
||||||
: i18n('icu:GroupV1--Migration--migrate--title');
|
: i18n('icu:GroupV1--Migration--migrate--title');
|
||||||
|
@ -82,13 +55,13 @@ export const GroupV1MigrationDialog: React.FunctionComponent<PropsType> =
|
||||||
};
|
};
|
||||||
if (hasMigrated) {
|
if (hasMigrated) {
|
||||||
primaryButtonText = i18n('icu:Confirmation--confirm');
|
primaryButtonText = i18n('icu:Confirmation--confirm');
|
||||||
onClickPrimaryButton = closeHandler;
|
onClickPrimaryButton = onClose;
|
||||||
} else {
|
} else {
|
||||||
primaryButtonText = i18n('icu:GroupV1--Migration--migrate');
|
primaryButtonText = i18n('icu:GroupV1--Migration--migrate');
|
||||||
onClickPrimaryButton = migrateHandler;
|
onClickPrimaryButton = onMigrate;
|
||||||
secondaryButtonProps = {
|
secondaryButtonProps = {
|
||||||
secondaryButtonText: i18n('icu:cancel'),
|
secondaryButtonText: i18n('icu:cancel'),
|
||||||
onClickSecondaryButton: closeHandler,
|
onClickSecondaryButton: onClose,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,7 +69,7 @@ export const GroupV1MigrationDialog: React.FunctionComponent<PropsType> =
|
||||||
<GroupDialog
|
<GroupDialog
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
onClickPrimaryButton={onClickPrimaryButton}
|
onClickPrimaryButton={onClickPrimaryButton}
|
||||||
onClose={closeHandler}
|
onClose={onClose}
|
||||||
primaryButtonText={primaryButtonText}
|
primaryButtonText={primaryButtonText}
|
||||||
title={title}
|
title={title}
|
||||||
{...secondaryButtonProps}
|
{...secondaryButtonProps}
|
||||||
|
|
|
@ -30,12 +30,7 @@ function focusRef(el: HTMLElement | null) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GroupV2JoinDialog = React.memo(function GroupV2JoinDialogInner(
|
export const GroupV2JoinDialog = React.memo(function GroupV2JoinDialogInner({
|
||||||
props: PropsType
|
|
||||||
) {
|
|
||||||
const [isWorking, setIsWorking] = React.useState(false);
|
|
||||||
const [isJoining, setIsJoining] = React.useState(false);
|
|
||||||
const {
|
|
||||||
approvalRequired,
|
approvalRequired,
|
||||||
avatar,
|
avatar,
|
||||||
groupDescription,
|
groupDescription,
|
||||||
|
@ -44,7 +39,9 @@ export const GroupV2JoinDialog = React.memo(function GroupV2JoinDialogInner(
|
||||||
memberCount,
|
memberCount,
|
||||||
onClose,
|
onClose,
|
||||||
title,
|
title,
|
||||||
} = props;
|
}: PropsType) {
|
||||||
|
const [isWorking, setIsWorking] = React.useState(false);
|
||||||
|
const [isJoining, setIsJoining] = React.useState(false);
|
||||||
|
|
||||||
const joinString = approvalRequired
|
const joinString = approvalRequired
|
||||||
? i18n('icu:GroupV2--join--request-to-join-button')
|
? i18n('icu:GroupV2--join--request-to-join-button')
|
||||||
|
|
|
@ -151,7 +151,6 @@ const useProps = (overrideProps: OverridePropsType = {}): PropsType => {
|
||||||
i18n,
|
i18n,
|
||||||
isMacOS: false,
|
isMacOS: false,
|
||||||
preferredWidthFromStorage: 320,
|
preferredWidthFromStorage: 320,
|
||||||
regionCode: 'US',
|
|
||||||
challengeStatus: 'idle',
|
challengeStatus: 'idle',
|
||||||
crashReportCount: 0,
|
crashReportCount: 0,
|
||||||
|
|
||||||
|
|
|
@ -104,7 +104,6 @@ export type PropsType = {
|
||||||
preferredWidthFromStorage: number;
|
preferredWidthFromStorage: number;
|
||||||
selectedConversationId: undefined | string;
|
selectedConversationId: undefined | string;
|
||||||
targetedMessageId: undefined | string;
|
targetedMessageId: undefined | string;
|
||||||
regionCode: string | undefined;
|
|
||||||
challengeStatus: 'idle' | 'required' | 'pending';
|
challengeStatus: 'idle' | 'required' | 'pending';
|
||||||
setChallengeStatus: (status: 'idle') => void;
|
setChallengeStatus: (status: 'idle') => void;
|
||||||
crashReportCount: number;
|
crashReportCount: number;
|
||||||
|
|
|
@ -24,13 +24,44 @@ type PropsType = {
|
||||||
Omit<ProfileEditorPropsType, 'onEditStateChanged' | 'onProfileChanged'>;
|
Omit<ProfileEditorPropsType, 'onEditStateChanged' | 'onProfileChanged'>;
|
||||||
|
|
||||||
export function ProfileEditorModal({
|
export function ProfileEditorModal({
|
||||||
|
aboutEmoji,
|
||||||
|
aboutText,
|
||||||
|
color,
|
||||||
|
conversationId,
|
||||||
|
deleteAvatarFromDisk,
|
||||||
|
deleteUsername,
|
||||||
|
familyName,
|
||||||
|
firstName,
|
||||||
|
hasCompletedUsernameLinkOnboarding,
|
||||||
hasError,
|
hasError,
|
||||||
i18n,
|
i18n,
|
||||||
|
initialEditState,
|
||||||
|
isUsernameDeletionEnabled,
|
||||||
|
markCompletedUsernameLinkOnboarding,
|
||||||
myProfileChanged,
|
myProfileChanged,
|
||||||
onSetSkinTone,
|
onSetSkinTone,
|
||||||
|
openUsernameReservationModal,
|
||||||
|
profileAvatarPath,
|
||||||
|
recentEmojis,
|
||||||
|
renderEditUsernameModalBody,
|
||||||
|
replaceAvatar,
|
||||||
|
resetUsernameLink,
|
||||||
|
saveAttachment,
|
||||||
|
saveAvatarToDisk,
|
||||||
|
setUsernameEditState,
|
||||||
|
setUsernameLinkColor,
|
||||||
|
showToast,
|
||||||
|
skinTone,
|
||||||
toggleProfileEditor,
|
toggleProfileEditor,
|
||||||
toggleProfileEditorHasError,
|
toggleProfileEditorHasError,
|
||||||
...restProps
|
userAvatarData,
|
||||||
|
username,
|
||||||
|
usernameCorrupted,
|
||||||
|
usernameEditState,
|
||||||
|
usernameLink,
|
||||||
|
usernameLinkColor,
|
||||||
|
usernameLinkCorrupted,
|
||||||
|
usernameLinkState,
|
||||||
}: PropsType): JSX.Element {
|
}: PropsType): JSX.Element {
|
||||||
const MODAL_TITLES_BY_EDIT_STATE: Record<EditState, string | undefined> = {
|
const MODAL_TITLES_BY_EDIT_STATE: Record<EditState, string | undefined> = {
|
||||||
[EditState.BetterAvatar]: i18n('icu:ProfileEditorModal--avatar'),
|
[EditState.BetterAvatar]: i18n('icu:ProfileEditorModal--avatar'),
|
||||||
|
@ -67,14 +98,47 @@ export function ProfileEditorModal({
|
||||||
title={modalTitle}
|
title={modalTitle}
|
||||||
>
|
>
|
||||||
<ProfileEditor
|
<ProfileEditor
|
||||||
{...restProps}
|
aboutEmoji={aboutEmoji}
|
||||||
|
aboutText={aboutText}
|
||||||
|
color={color}
|
||||||
|
conversationId={conversationId}
|
||||||
|
deleteAvatarFromDisk={deleteAvatarFromDisk}
|
||||||
|
deleteUsername={deleteUsername}
|
||||||
|
familyName={familyName}
|
||||||
|
firstName={firstName}
|
||||||
|
hasCompletedUsernameLinkOnboarding={hasCompletedUsernameLinkOnboarding}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
|
initialEditState={initialEditState}
|
||||||
|
isUsernameDeletionEnabled={isUsernameDeletionEnabled}
|
||||||
|
markCompletedUsernameLinkOnboarding={
|
||||||
|
markCompletedUsernameLinkOnboarding
|
||||||
|
}
|
||||||
onEditStateChanged={editState => {
|
onEditStateChanged={editState => {
|
||||||
setModalTitle(MODAL_TITLES_BY_EDIT_STATE[editState]);
|
setModalTitle(MODAL_TITLES_BY_EDIT_STATE[editState]);
|
||||||
}}
|
}}
|
||||||
onProfileChanged={myProfileChanged}
|
onProfileChanged={myProfileChanged}
|
||||||
onSetSkinTone={onSetSkinTone}
|
onSetSkinTone={onSetSkinTone}
|
||||||
|
openUsernameReservationModal={openUsernameReservationModal}
|
||||||
|
profileAvatarPath={profileAvatarPath}
|
||||||
|
recentEmojis={recentEmojis}
|
||||||
|
renderEditUsernameModalBody={renderEditUsernameModalBody}
|
||||||
|
replaceAvatar={replaceAvatar}
|
||||||
|
resetUsernameLink={resetUsernameLink}
|
||||||
|
saveAttachment={saveAttachment}
|
||||||
|
saveAvatarToDisk={saveAvatarToDisk}
|
||||||
|
setUsernameEditState={setUsernameEditState}
|
||||||
|
setUsernameLinkColor={setUsernameLinkColor}
|
||||||
|
showToast={showToast}
|
||||||
|
skinTone={skinTone}
|
||||||
toggleProfileEditor={toggleProfileEditor}
|
toggleProfileEditor={toggleProfileEditor}
|
||||||
|
userAvatarData={userAvatarData}
|
||||||
|
username={username}
|
||||||
|
usernameCorrupted={usernameCorrupted}
|
||||||
|
usernameEditState={usernameEditState}
|
||||||
|
usernameLink={usernameLink}
|
||||||
|
usernameLinkColor={usernameLinkColor}
|
||||||
|
usernameLinkCorrupted={usernameLinkCorrupted}
|
||||||
|
usernameLinkState={usernameLinkState}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|
|
@ -14,10 +14,10 @@ export type PropsType = {
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
files: ReadonlyArray<File>;
|
files: ReadonlyArray<File>;
|
||||||
}) => void;
|
}) => void;
|
||||||
renderCompositionArea: () => JSX.Element;
|
renderCompositionArea: (conversationId: string) => JSX.Element;
|
||||||
renderConversationHeader: () => JSX.Element;
|
renderConversationHeader: (conversationId: string) => JSX.Element;
|
||||||
renderTimeline: () => JSX.Element;
|
renderTimeline: (conversationId: string) => JSX.Element;
|
||||||
renderPanel: () => JSX.Element | undefined;
|
renderPanel: (conversationId: string) => JSX.Element | undefined;
|
||||||
shouldHideConversationView?: boolean;
|
shouldHideConversationView?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -121,20 +121,20 @@ export function ConversationView({
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div className="ConversationView__header">
|
<div className="ConversationView__header">
|
||||||
{renderConversationHeader()}
|
{renderConversationHeader(conversationId)}
|
||||||
</div>
|
</div>
|
||||||
<div className="ConversationView__pane">
|
<div className="ConversationView__pane">
|
||||||
<div className="ConversationView__timeline--container">
|
<div className="ConversationView__timeline--container">
|
||||||
<div aria-live="polite" className="ConversationView__timeline">
|
<div aria-live="polite" className="ConversationView__timeline">
|
||||||
{renderTimeline()}
|
{renderTimeline(conversationId)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="ConversationView__composition-area">
|
<div className="ConversationView__composition-area">
|
||||||
{renderCompositionArea()}
|
{renderCompositionArea(conversationId)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{renderPanel()}
|
{renderPanel(conversationId)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,6 @@ export type PropsType = PropsDataType & PropsHousekeepingType;
|
||||||
export function GroupV1Migration(props: PropsType): React.ReactElement {
|
export function GroupV1Migration(props: PropsType): React.ReactElement {
|
||||||
const {
|
const {
|
||||||
areWeInvited,
|
areWeInvited,
|
||||||
conversationId,
|
|
||||||
droppedMembers,
|
droppedMembers,
|
||||||
getPreferredBadge,
|
getPreferredBadge,
|
||||||
i18n,
|
i18n,
|
||||||
|
@ -80,13 +79,12 @@ export function GroupV1Migration(props: PropsType): React.ReactElement {
|
||||||
{showingDialog ? (
|
{showingDialog ? (
|
||||||
<GroupV1MigrationDialog
|
<GroupV1MigrationDialog
|
||||||
areWeInvited={areWeInvited}
|
areWeInvited={areWeInvited}
|
||||||
conversationId={conversationId}
|
|
||||||
droppedMembers={droppedMembers}
|
droppedMembers={droppedMembers}
|
||||||
getPreferredBadge={getPreferredBadge}
|
getPreferredBadge={getPreferredBadge}
|
||||||
hasMigrated
|
hasMigrated
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
invitedMembers={invitedMembers}
|
invitedMembers={invitedMembers}
|
||||||
migrate={() => log.warn('GroupV1Migration: Modal called migrate()')}
|
onMigrate={() => log.warn('GroupV1Migration: Modal called migrate()')}
|
||||||
onClose={dismissDialog}
|
onClose={dismissDialog}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -452,7 +452,9 @@ const useProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
||||||
isIncomingMessageRequest: overrideProps.isIncomingMessageRequest ?? false,
|
isIncomingMessageRequest: overrideProps.isIncomingMessageRequest ?? false,
|
||||||
items: overrideProps.items ?? Object.keys(items),
|
items: overrideProps.items ?? Object.keys(items),
|
||||||
messageChangeCounter: 0,
|
messageChangeCounter: 0,
|
||||||
scrollToIndex: overrideProps.scrollToIndex,
|
messageLoadingState: null,
|
||||||
|
isNearBottom: null,
|
||||||
|
scrollToIndex: overrideProps.scrollToIndex ?? null,
|
||||||
scrollToIndexCounter: 0,
|
scrollToIndexCounter: 0,
|
||||||
shouldShowMiniPlayer: Boolean(overrideProps.shouldShowMiniPlayer),
|
shouldShowMiniPlayer: Boolean(overrideProps.shouldShowMiniPlayer),
|
||||||
totalUnseen: overrideProps.totalUnseen ?? 0,
|
totalUnseen: overrideProps.totalUnseen ?? 0,
|
||||||
|
|
|
@ -70,11 +70,11 @@ export type PropsDataType = {
|
||||||
haveNewest: boolean;
|
haveNewest: boolean;
|
||||||
haveOldest: boolean;
|
haveOldest: boolean;
|
||||||
messageChangeCounter: number;
|
messageChangeCounter: number;
|
||||||
messageLoadingState?: TimelineMessageLoadingState;
|
messageLoadingState: TimelineMessageLoadingState | null;
|
||||||
isNearBottom?: boolean;
|
isNearBottom: boolean | null;
|
||||||
items: ReadonlyArray<string>;
|
items: ReadonlyArray<string>;
|
||||||
oldestUnseenIndex?: number;
|
oldestUnseenIndex: number | null;
|
||||||
scrollToIndex?: number;
|
scrollToIndex: number | null;
|
||||||
scrollToIndexCounter: number;
|
scrollToIndexCounter: number;
|
||||||
totalUnseen: number;
|
totalUnseen: number;
|
||||||
};
|
};
|
||||||
|
@ -563,7 +563,7 @@ export class Timeline extends React.Component<
|
||||||
case ScrollAnchor.ScrollToBottom:
|
case ScrollAnchor.ScrollToBottom:
|
||||||
return { scrollBottom: 0 };
|
return { scrollBottom: 0 };
|
||||||
case ScrollAnchor.ScrollToIndex:
|
case ScrollAnchor.ScrollToIndex:
|
||||||
if (scrollToIndex === undefined) {
|
if (scrollToIndex == null) {
|
||||||
assertDev(
|
assertDev(
|
||||||
false,
|
false,
|
||||||
'<Timeline> got "scroll to index" scroll anchor, but no index'
|
'<Timeline> got "scroll to index" scroll anchor, but no index'
|
||||||
|
|
|
@ -63,7 +63,6 @@ const createProps = (
|
||||||
selectedConversationIds
|
selectedConversationIds
|
||||||
)}
|
)}
|
||||||
regionCode="US"
|
regionCode="US"
|
||||||
getPreferredBadge={() => undefined}
|
|
||||||
ourE164={undefined}
|
ourE164={undefined}
|
||||||
ourUsername={undefined}
|
ourUsername={undefined}
|
||||||
theme={ThemeType.light}
|
theme={ThemeType.light}
|
||||||
|
|
|
@ -21,7 +21,6 @@ import { parseAndFormatPhoneNumber } from '../../../../util/libphonenumberInstan
|
||||||
import type { ParsedE164Type } from '../../../../util/libphonenumberInstance';
|
import type { ParsedE164Type } from '../../../../util/libphonenumberInstance';
|
||||||
import { filterAndSortConversationsByRecent } from '../../../../util/filterAndSortConversations';
|
import { filterAndSortConversationsByRecent } from '../../../../util/filterAndSortConversations';
|
||||||
import type { ConversationType } from '../../../../state/ducks/conversations';
|
import type { ConversationType } from '../../../../state/ducks/conversations';
|
||||||
import type { PreferredBadgeSelectorType } from '../../../../state/selectors/badges';
|
|
||||||
import type {
|
import type {
|
||||||
UUIDFetchStateKeyType,
|
UUIDFetchStateKeyType,
|
||||||
UUIDFetchStateType,
|
UUIDFetchStateType,
|
||||||
|
@ -50,7 +49,6 @@ export type StatePropsType = {
|
||||||
regionCode: string | undefined;
|
regionCode: string | undefined;
|
||||||
candidateContacts: ReadonlyArray<ConversationType>;
|
candidateContacts: ReadonlyArray<ConversationType>;
|
||||||
conversationIdsAlreadyInGroup: Set<string>;
|
conversationIdsAlreadyInGroup: Set<string>;
|
||||||
getPreferredBadge: PreferredBadgeSelectorType;
|
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
theme: ThemeType;
|
theme: ThemeType;
|
||||||
maxGroupSize: number;
|
maxGroupSize: number;
|
||||||
|
|
|
@ -119,7 +119,6 @@ const createProps = (
|
||||||
candidateContacts={allCandidateContacts}
|
candidateContacts={allCandidateContacts}
|
||||||
selectedContacts={[]}
|
selectedContacts={[]}
|
||||||
regionCode="US"
|
regionCode="US"
|
||||||
getPreferredBadge={() => undefined}
|
|
||||||
theme={ThemeType.light}
|
theme={ThemeType.light}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
lookupConversationWithoutServiceId={makeFakeLookupConversationWithoutServiceId()}
|
lookupConversationWithoutServiceId={makeFakeLookupConversationWithoutServiceId()}
|
||||||
|
|
|
@ -29,7 +29,7 @@ export type OwnProps = {
|
||||||
|
|
||||||
export type Props = OwnProps;
|
export type Props = OwnProps;
|
||||||
|
|
||||||
function renderBody({ pack, i18n }: Props) {
|
function renderBody({ pack, i18n }: Pick<Props, 'i18n' | 'pack'>) {
|
||||||
if (!pack) {
|
if (!pack) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -73,10 +73,8 @@ function renderBody({ pack, i18n }: Props) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const StickerPreviewModal = React.memo(function StickerPreviewModalInner(
|
export const StickerPreviewModal = React.memo(
|
||||||
props: Props
|
function StickerPreviewModalInner({
|
||||||
) {
|
|
||||||
const {
|
|
||||||
closeStickerPackPreview,
|
closeStickerPackPreview,
|
||||||
downloadStickerPack,
|
downloadStickerPack,
|
||||||
i18n,
|
i18n,
|
||||||
|
@ -84,7 +82,7 @@ export const StickerPreviewModal = React.memo(function StickerPreviewModalInner(
|
||||||
onClose,
|
onClose,
|
||||||
pack,
|
pack,
|
||||||
uninstallStickerPack,
|
uninstallStickerPack,
|
||||||
} = props;
|
}: Props) {
|
||||||
const [confirmingUninstall, setConfirmingUninstall] = React.useState(false);
|
const [confirmingUninstall, setConfirmingUninstall] = React.useState(false);
|
||||||
|
|
||||||
// Restore focus on teardown
|
// Restore focus on teardown
|
||||||
|
@ -218,8 +216,9 @@ export const StickerPreviewModal = React.memo(function StickerPreviewModalInner(
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
title={i18n('icu:stickers--StickerPreview--Title')}
|
title={i18n('icu:stickers--StickerPreview--Title')}
|
||||||
>
|
>
|
||||||
{renderBody(props)}
|
{renderBody({ pack, i18n })}
|
||||||
</Modal>
|
</Modal>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
|
@ -4,8 +4,6 @@
|
||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect } from 'react';
|
||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import type { StateType } from '../state/reducer';
|
|
||||||
import * as KeyboardLayout from '../services/keyboardLayout';
|
import * as KeyboardLayout from '../services/keyboardLayout';
|
||||||
import { getHasPanelOpen } from '../state/selectors/conversations';
|
import { getHasPanelOpen } from '../state/selectors/conversations';
|
||||||
import { isInFullScreenCall } from '../state/selectors/calling';
|
import { isInFullScreenCall } from '../state/selectors/calling';
|
||||||
|
@ -33,11 +31,11 @@ function useHasPanels(): boolean {
|
||||||
}
|
}
|
||||||
|
|
||||||
function useHasGlobalModal(): boolean {
|
function useHasGlobalModal(): boolean {
|
||||||
return useSelector<StateType, boolean>(isShowingAnyModal);
|
return useSelector(isShowingAnyModal);
|
||||||
}
|
}
|
||||||
|
|
||||||
function useHasCalling(): boolean {
|
function useHasCalling(): boolean {
|
||||||
return useSelector<StateType, boolean>(isInFullScreenCall);
|
return useSelector(isInFullScreenCall);
|
||||||
}
|
}
|
||||||
|
|
||||||
function useHasAnyOverlay(): boolean {
|
function useHasAnyOverlay(): boolean {
|
||||||
|
|
|
@ -11,6 +11,8 @@ import type { StateType as RootStateType } from '../reducer';
|
||||||
import { showToast } from './toast';
|
import { showToast } from './toast';
|
||||||
import type { ShowToastActionType } from './toast';
|
import type { ShowToastActionType } from './toast';
|
||||||
import type { PromiseAction } from '../util';
|
import type { PromiseAction } from '../util';
|
||||||
|
import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions';
|
||||||
|
import { useBoundActions } from '../../hooks/useBoundActions';
|
||||||
|
|
||||||
// State
|
// State
|
||||||
|
|
||||||
|
@ -44,6 +46,10 @@ export const actions = {
|
||||||
eraseCrashReports,
|
eraseCrashReports,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useCrashReportsActions = (): BoundActionCreatorsMapObject<
|
||||||
|
typeof actions
|
||||||
|
> => useBoundActions(actions);
|
||||||
|
|
||||||
function setCrashReportCount(count: number): SetCrashReportCountActionType {
|
function setCrashReportCount(count: number): SetCrashReportCountActionType {
|
||||||
return { type: SET_COUNT, payload: count };
|
return { type: SET_COUNT, payload: count };
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@ import type { ReadonlyDeep } from 'type-fest';
|
||||||
import { SocketStatus } from '../../types/SocketStatus';
|
import { SocketStatus } from '../../types/SocketStatus';
|
||||||
import { trigger } from '../../shims/events';
|
import { trigger } from '../../shims/events';
|
||||||
import { assignWithNoUnnecessaryAllocation } from '../../util/assignWithNoUnnecessaryAllocation';
|
import { assignWithNoUnnecessaryAllocation } from '../../util/assignWithNoUnnecessaryAllocation';
|
||||||
|
import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions';
|
||||||
|
import { useBoundActions } from '../../hooks/useBoundActions';
|
||||||
|
|
||||||
// State
|
// State
|
||||||
|
|
||||||
|
@ -113,6 +115,10 @@ export const actions = {
|
||||||
setOutage,
|
setOutage,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useNetworkActions = (): BoundActionCreatorsMapObject<
|
||||||
|
typeof actions
|
||||||
|
> => useBoundActions(actions);
|
||||||
|
|
||||||
// Reducer
|
// Reducer
|
||||||
|
|
||||||
export function getEmptyState(): NetworkStateType {
|
export function getEmptyState(): NetworkStateType {
|
||||||
|
|
|
@ -15,6 +15,8 @@ import {
|
||||||
import * as log from '../../logging/log';
|
import * as log from '../../logging/log';
|
||||||
import * as Errors from '../../types/errors';
|
import * as Errors from '../../types/errors';
|
||||||
import type { StateType as RootStateType } from '../reducer';
|
import type { StateType as RootStateType } from '../reducer';
|
||||||
|
import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions';
|
||||||
|
import { useBoundActions } from '../../hooks/useBoundActions';
|
||||||
|
|
||||||
export type SafetyNumberContactType = ReadonlyDeep<{
|
export type SafetyNumberContactType = ReadonlyDeep<{
|
||||||
safetyNumber: SafetyNumberType;
|
safetyNumber: SafetyNumberType;
|
||||||
|
@ -174,6 +176,10 @@ export const actions = {
|
||||||
toggleVerified,
|
toggleVerified,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useSafetyNumberActions = (): BoundActionCreatorsMapObject<
|
||||||
|
typeof actions
|
||||||
|
> => useBoundActions(actions);
|
||||||
|
|
||||||
export function getEmptyState(): SafetyNumberStateType {
|
export function getEmptyState(): SafetyNumberStateType {
|
||||||
return {
|
return {
|
||||||
contacts: {},
|
contacts: {},
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
import type { ReadonlyDeep } from 'type-fest';
|
import type { ReadonlyDeep } from 'type-fest';
|
||||||
import { trigger } from '../../shims/events';
|
import { trigger } from '../../shims/events';
|
||||||
|
|
||||||
import type { LocaleMessagesType } from '../../types/I18N';
|
import type { LocaleMessagesType } from '../../types/I18N';
|
||||||
import type { LocalizerType } from '../../types/Util';
|
import type { LocalizerType } from '../../types/Util';
|
||||||
import type { MenuOptionsType } from '../../types/menu';
|
import type { MenuOptionsType } from '../../types/menu';
|
||||||
|
@ -11,6 +10,8 @@ import type { NoopActionType } from './noop';
|
||||||
import type { AciString, PniString } from '../../types/ServiceId';
|
import type { AciString, PniString } from '../../types/ServiceId';
|
||||||
import OS from '../../util/os/osMain';
|
import OS from '../../util/os/osMain';
|
||||||
import { ThemeType } from '../../types/Util';
|
import { ThemeType } from '../../types/Util';
|
||||||
|
import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions';
|
||||||
|
import { useBoundActions } from '../../hooks/useBoundActions';
|
||||||
|
|
||||||
// State
|
// State
|
||||||
|
|
||||||
|
@ -73,6 +74,10 @@ export const actions = {
|
||||||
manualReconnect,
|
manualReconnect,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useUserActions = (): BoundActionCreatorsMapObject<
|
||||||
|
typeof actions
|
||||||
|
> => useBoundActions(actions);
|
||||||
|
|
||||||
function eraseStorageServiceState(): EraseStorageServiceStateAction {
|
function eraseStorageServiceState(): EraseStorageServiceStateAction {
|
||||||
return {
|
return {
|
||||||
type: ERASE_STORAGE_SERVICE,
|
type: ERASE_STORAGE_SERVICE,
|
||||||
|
|
|
@ -9,12 +9,12 @@ import { Provider } from 'react-redux';
|
||||||
import type { Store } from 'redux';
|
import type { Store } from 'redux';
|
||||||
|
|
||||||
import { ModalHost } from '../../components/ModalHost';
|
import { ModalHost } from '../../components/ModalHost';
|
||||||
import type { PropsType } from '../smart/GroupV2JoinDialog';
|
import type { SmartGroupV2JoinDialogProps } from '../smart/GroupV2JoinDialog';
|
||||||
import { SmartGroupV2JoinDialog } from '../smart/GroupV2JoinDialog';
|
import { SmartGroupV2JoinDialog } from '../smart/GroupV2JoinDialog';
|
||||||
|
|
||||||
export const createGroupV2JoinModal = (
|
export const createGroupV2JoinModal = (
|
||||||
store: Store,
|
store: Store,
|
||||||
props: PropsType
|
props: SmartGroupV2JoinDialogProps
|
||||||
): React.ReactElement => {
|
): React.ReactElement => {
|
||||||
const { onClose } = props;
|
const { onClose } = props;
|
||||||
|
|
||||||
|
|
12
ts/state/selectors/app.ts
Normal file
12
ts/state/selectors/app.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
// Copyright 2024 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import type { StateType } from '../reducer';
|
||||||
|
import type { AppStateType } from '../ducks/app';
|
||||||
|
|
||||||
|
export const getApp = (state: StateType): AppStateType => state.app;
|
||||||
|
|
||||||
|
export const getHasInitialLoadCompleted = createSelector(
|
||||||
|
getApp,
|
||||||
|
({ hasInitialLoadCompleted }) => hasInitialLoadCompleted
|
||||||
|
);
|
|
@ -23,6 +23,36 @@ export type CallStateType = DirectCallStateType | GroupCallStateType;
|
||||||
|
|
||||||
const getCalling = (state: StateType): CallingStateType => state.calling;
|
const getCalling = (state: StateType): CallingStateType => state.calling;
|
||||||
|
|
||||||
|
export const getAvailableMicrophones = createSelector(
|
||||||
|
getCalling,
|
||||||
|
({ availableMicrophones }) => availableMicrophones
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getSelectedMicrophone = createSelector(
|
||||||
|
getCalling,
|
||||||
|
({ selectedMicrophone }) => selectedMicrophone
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getAvailableSpeakers = createSelector(
|
||||||
|
getCalling,
|
||||||
|
({ availableSpeakers }) => availableSpeakers
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getSelectedSpeaker = createSelector(
|
||||||
|
getCalling,
|
||||||
|
({ selectedSpeaker }) => selectedSpeaker
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getAvailableCameras = createSelector(
|
||||||
|
getCalling,
|
||||||
|
({ availableCameras }) => availableCameras
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getSelectedCamera = createSelector(
|
||||||
|
getCalling,
|
||||||
|
({ selectedCamera }) => selectedCamera
|
||||||
|
);
|
||||||
|
|
||||||
export const getActiveCallState = createSelector(
|
export const getActiveCallState = createSelector(
|
||||||
getCalling,
|
getCalling,
|
||||||
(state: CallingStateType) => state.activeCallState
|
(state: CallingStateType) => state.activeCallState
|
||||||
|
|
|
@ -513,6 +513,13 @@ export const getComposerUUIDFetchState = createSelector(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const getHasContactSpoofingReview = createSelector(
|
||||||
|
getConversations,
|
||||||
|
(state: ConversationsStateType): boolean => {
|
||||||
|
return state.hasContactSpoofingReview;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
function isTrusted(conversation: ConversationType): boolean {
|
function isTrusted(conversation: ConversationType): boolean {
|
||||||
if (conversation.type === 'group') {
|
if (conversation.type === 'group') {
|
||||||
return true;
|
return true;
|
||||||
|
@ -986,10 +993,10 @@ export function _conversationMessagesSelector(
|
||||||
conversation: ConversationMessageType
|
conversation: ConversationMessageType
|
||||||
): TimelinePropsType {
|
): TimelinePropsType {
|
||||||
const {
|
const {
|
||||||
isNearBottom,
|
isNearBottom = null,
|
||||||
messageChangeCounter,
|
messageChangeCounter,
|
||||||
messageIds,
|
messageIds,
|
||||||
messageLoadingState,
|
messageLoadingState = null,
|
||||||
metrics,
|
metrics,
|
||||||
scrollToMessageCounter,
|
scrollToMessageCounter,
|
||||||
scrollToMessageId,
|
scrollToMessageId,
|
||||||
|
@ -1009,10 +1016,10 @@ export function _conversationMessagesSelector(
|
||||||
|
|
||||||
const oldestUnseenIndex = oldestUnseen
|
const oldestUnseenIndex = oldestUnseen
|
||||||
? messageIds.findIndex(id => id === oldestUnseen.id)
|
? messageIds.findIndex(id => id === oldestUnseen.id)
|
||||||
: undefined;
|
: null;
|
||||||
const scrollToIndex = scrollToMessageId
|
const scrollToIndex = scrollToMessageId
|
||||||
? messageIds.findIndex(id => id === scrollToMessageId)
|
? messageIds.findIndex(id => id === scrollToMessageId)
|
||||||
: undefined;
|
: null;
|
||||||
const { totalUnseen } = metrics;
|
const { totalUnseen } = metrics;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -1025,9 +1032,9 @@ export function _conversationMessagesSelector(
|
||||||
oldestUnseenIndex:
|
oldestUnseenIndex:
|
||||||
isNumber(oldestUnseenIndex) && oldestUnseenIndex >= 0
|
isNumber(oldestUnseenIndex) && oldestUnseenIndex >= 0
|
||||||
? oldestUnseenIndex
|
? oldestUnseenIndex
|
||||||
: undefined,
|
: null,
|
||||||
scrollToIndex:
|
scrollToIndex:
|
||||||
isNumber(scrollToIndex) && scrollToIndex >= 0 ? scrollToIndex : undefined,
|
isNumber(scrollToIndex) && scrollToIndex >= 0 ? scrollToIndex : null,
|
||||||
scrollToIndexCounter: scrollToMessageCounter,
|
scrollToIndexCounter: scrollToMessageCounter,
|
||||||
totalUnseen,
|
totalUnseen,
|
||||||
};
|
};
|
||||||
|
@ -1065,6 +1072,9 @@ export const getConversationMessagesSelector = createSelector(
|
||||||
scrollToIndexCounter: 0,
|
scrollToIndexCounter: 0,
|
||||||
totalUnseen: 0,
|
totalUnseen: 0,
|
||||||
items: [],
|
items: [],
|
||||||
|
isNearBottom: null,
|
||||||
|
oldestUnseenIndex: null,
|
||||||
|
scrollToIndex: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
18
ts/state/selectors/crashReports.ts
Normal file
18
ts/state/selectors/crashReports.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
// Copyright 2024 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import type { StateType } from '../reducer';
|
||||||
|
import type { CrashReportsStateType } from '../ducks/crashReports';
|
||||||
|
|
||||||
|
const getCrashReports = (state: StateType): CrashReportsStateType =>
|
||||||
|
state.crashReports;
|
||||||
|
|
||||||
|
export const getCrashReportsIsPending = createSelector(
|
||||||
|
getCrashReports,
|
||||||
|
({ isPending }) => isPending
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getCrashReportCount = createSelector(
|
||||||
|
getCrashReports,
|
||||||
|
({ count }) => count
|
||||||
|
);
|
|
@ -21,3 +21,43 @@ export const isShowingAnyModal = createSelector(
|
||||||
return Boolean(value);
|
return Boolean(value);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const getContactModalState = createSelector(
|
||||||
|
getGlobalModalsState,
|
||||||
|
({ contactModalState }) => contactModalState
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getIsStoriesSettingsVisible = createSelector(
|
||||||
|
getGlobalModalsState,
|
||||||
|
({ isStoriesSettingsVisible }) => isStoriesSettingsVisible
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getSafetyNumberChangedBlockingData = createSelector(
|
||||||
|
getGlobalModalsState,
|
||||||
|
({ safetyNumberChangedBlockingData }) => safetyNumberChangedBlockingData
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getDeleteMessagesProps = createSelector(
|
||||||
|
getGlobalModalsState,
|
||||||
|
({ deleteMessagesProps }) => deleteMessagesProps
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getEditHistoryMessages = createSelector(
|
||||||
|
getGlobalModalsState,
|
||||||
|
({ editHistoryMessages }) => editHistoryMessages
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getForwardMessagesProps = createSelector(
|
||||||
|
getGlobalModalsState,
|
||||||
|
({ forwardMessagesProps }) => forwardMessagesProps
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getProfileEditorHasError = createSelector(
|
||||||
|
getGlobalModalsState,
|
||||||
|
({ profileEditorHasError }) => profileEditorHasError
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getProfileEditorInitialEditState = createSelector(
|
||||||
|
getGlobalModalsState,
|
||||||
|
({ profileEditorInitialEditState }) => profileEditorInitialEditState
|
||||||
|
);
|
||||||
|
|
|
@ -10,6 +10,21 @@ import { SocketStatus } from '../../types/SocketStatus';
|
||||||
|
|
||||||
const getNetwork = (state: StateType): NetworkStateType => state.network;
|
const getNetwork = (state: StateType): NetworkStateType => state.network;
|
||||||
|
|
||||||
|
export const getNetworkIsOnline = createSelector(
|
||||||
|
getNetwork,
|
||||||
|
({ isOnline }) => isOnline
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getNetworkIsOutage = createSelector(
|
||||||
|
getNetwork,
|
||||||
|
({ isOutage }) => isOutage
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getNetworkSocketStatus = createSelector(
|
||||||
|
getNetwork,
|
||||||
|
({ socketStatus }) => socketStatus
|
||||||
|
);
|
||||||
|
|
||||||
export const hasNetworkDialog = createSelector(
|
export const hasNetworkDialog = createSelector(
|
||||||
getNetwork,
|
getNetwork,
|
||||||
isDone,
|
isDone,
|
||||||
|
@ -31,6 +46,11 @@ export const hasNetworkDialog = createSelector(
|
||||||
socketStatus === SocketStatus.CLOSING)
|
socketStatus === SocketStatus.CLOSING)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const getChallengeStatus = createSelector(
|
||||||
|
getNetwork,
|
||||||
|
({ challengeStatus }) => challengeStatus
|
||||||
|
);
|
||||||
|
|
||||||
export const isChallengePending = createSelector(
|
export const isChallengePending = createSelector(
|
||||||
getNetwork,
|
getNetwork,
|
||||||
({ challengeStatus }) => challengeStatus === 'pending'
|
({ challengeStatus }) => challengeStatus === 'pending'
|
||||||
|
|
11
ts/state/selectors/toast.ts
Normal file
11
ts/state/selectors/toast.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
// Copyright 2024 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import type { StateType } from '../reducer';
|
||||||
|
import type { ToastStateType } from '../ducks/toast';
|
||||||
|
|
||||||
|
export function getToastState(state: StateType): ToastStateType {
|
||||||
|
return state.toast;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getToast = createSelector(getToastState, ({ toast }) => toast);
|
|
@ -11,6 +11,26 @@ import type { UpdatesStateType } from '../ducks/updates';
|
||||||
export const getUpdatesState = (state: Readonly<StateType>): UpdatesStateType =>
|
export const getUpdatesState = (state: Readonly<StateType>): UpdatesStateType =>
|
||||||
state.updates;
|
state.updates;
|
||||||
|
|
||||||
|
export const getUpdateDialogType = createSelector(
|
||||||
|
getUpdatesState,
|
||||||
|
({ dialogType }) => dialogType
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getUpdateVersion = createSelector(
|
||||||
|
getUpdatesState,
|
||||||
|
({ version }) => version
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getUpdateDownloadSize = createSelector(
|
||||||
|
getUpdatesState,
|
||||||
|
({ downloadSize }) => downloadSize
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getUpdateDownloadedSize = createSelector(
|
||||||
|
getUpdatesState,
|
||||||
|
({ downloadedSize }) => downloadedSize
|
||||||
|
);
|
||||||
|
|
||||||
export const isUpdateDialogVisible = createSelector(
|
export const isUpdateDialogVisible = createSelector(
|
||||||
getUpdatesState,
|
getUpdatesState,
|
||||||
({ dialogType, didSnooze }) => {
|
({ dialogType, didSnooze }) => {
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
// Copyright 2024 Signal Messenger, LLC
|
// Copyright 2024 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
import React, { memo } from 'react';
|
||||||
import React from 'react';
|
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { AboutContactModal } from '../../components/conversation/AboutContactModal';
|
import { AboutContactModal } from '../../components/conversation/AboutContactModal';
|
||||||
import { isSignalConnection } from '../../util/getSignalConnections';
|
import { isSignalConnection } from '../../util/getSignalConnections';
|
||||||
import { getIntl } from '../selectors/user';
|
import { getIntl } from '../selectors/user';
|
||||||
|
@ -12,7 +10,7 @@ import { getConversationSelector } from '../selectors/conversations';
|
||||||
import { useConversationsActions } from '../ducks/conversations';
|
import { useConversationsActions } from '../ducks/conversations';
|
||||||
import { useGlobalModalActions } from '../ducks/globalModals';
|
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||||
|
|
||||||
export function SmartAboutContactModal(): JSX.Element | null {
|
export const SmartAboutContactModal = memo(function SmartAboutContactModal() {
|
||||||
const i18n = useSelector(getIntl);
|
const i18n = useSelector(getIntl);
|
||||||
const globalModals = useSelector(getGlobalModalsState);
|
const globalModals = useSelector(getGlobalModalsState);
|
||||||
const { aboutContactModalContactId: contactId } = globalModals;
|
const { aboutContactModalContactId: contactId } = globalModals;
|
||||||
|
@ -44,4 +42,4 @@ export function SmartAboutContactModal(): JSX.Element | null {
|
||||||
onClose={toggleAboutContactModal}
|
onClose={toggleAboutContactModal}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
|
@ -1,37 +1,47 @@
|
||||||
// Copyright 2022 Signal Messenger, LLC
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { connect } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { mapDispatchToProps } from '../actions';
|
import React, { memo } from 'react';
|
||||||
import { AddUserToAnotherGroupModal } from '../../components/AddUserToAnotherGroupModal';
|
import { AddUserToAnotherGroupModal } from '../../components/AddUserToAnotherGroupModal';
|
||||||
import type { StateType } from '../reducer';
|
|
||||||
import {
|
import {
|
||||||
getAllGroupsWithInviteAccess,
|
getAllGroupsWithInviteAccess,
|
||||||
getContactSelector,
|
getContactSelector,
|
||||||
} from '../selectors/conversations';
|
} from '../selectors/conversations';
|
||||||
import { getIntl, getRegionCode, getTheme } from '../selectors/user';
|
import { getIntl, getRegionCode } from '../selectors/user';
|
||||||
|
import { useToastActions } from '../ducks/toast';
|
||||||
|
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||||
|
import { useConversationsActions } from '../ducks/conversations';
|
||||||
|
|
||||||
export type Props = {
|
export type SmartAddUserToAnotherGroupModalProps = Readonly<{
|
||||||
contactID: string;
|
contactID: string;
|
||||||
};
|
}>;
|
||||||
|
|
||||||
const mapStateToProps = (state: StateType, props: Props) => {
|
export const SmartAddUserToAnotherGroupModal = memo(
|
||||||
const candidateConversations = getAllGroupsWithInviteAccess(state);
|
function SmartAddUserToAnotherGroupModal({
|
||||||
const getContact = getContactSelector(state);
|
contactID,
|
||||||
|
}: SmartAddUserToAnotherGroupModalProps) {
|
||||||
|
const i18n = useSelector(getIntl);
|
||||||
|
const candidateConversations = useSelector(getAllGroupsWithInviteAccess);
|
||||||
|
const getContact = useSelector(getContactSelector);
|
||||||
|
const regionCode = useSelector(getRegionCode);
|
||||||
|
|
||||||
const regionCode = getRegionCode(state);
|
const { toggleAddUserToAnotherGroupModal } = useGlobalModalActions();
|
||||||
|
const { addMembersToGroup } = useConversationsActions();
|
||||||
|
const { showToast } = useToastActions();
|
||||||
|
|
||||||
return {
|
const contact = getContact(contactID);
|
||||||
contact: getContact(props.contactID),
|
|
||||||
i18n: getIntl(state),
|
|
||||||
theme: getTheme(state),
|
|
||||||
candidateConversations,
|
|
||||||
regionCode,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
return (
|
||||||
|
<AddUserToAnotherGroupModal
|
||||||
export const SmartAddUserToAnotherGroupModal = smart(
|
contact={contact}
|
||||||
AddUserToAnotherGroupModal
|
i18n={i18n}
|
||||||
|
candidateConversations={candidateConversations}
|
||||||
|
regionCode={regionCode}
|
||||||
|
toggleAddUserToAnotherGroupModal={toggleAddUserToAnotherGroupModal}
|
||||||
|
addMembersToGroup={addMembersToGroup}
|
||||||
|
showToast={showToast}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,21 +1,20 @@
|
||||||
// Copyright 2022 Signal Messenger, LLC
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
import React, { memo } from 'react';
|
||||||
import React from 'react';
|
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { MediaGallery } from '../../components/conversation/media-gallery/MediaGallery';
|
import { MediaGallery } from '../../components/conversation/media-gallery/MediaGallery';
|
||||||
import { getMediaGalleryState } from '../selectors/mediaGallery';
|
import { getMediaGalleryState } from '../selectors/mediaGallery';
|
||||||
import { useConversationsActions } from '../ducks/conversations';
|
import { useConversationsActions } from '../ducks/conversations';
|
||||||
import { useLightboxActions } from '../ducks/lightbox';
|
import { useLightboxActions } from '../ducks/lightbox';
|
||||||
|
|
||||||
import { useMediaGalleryActions } from '../ducks/mediaGallery';
|
import { useMediaGalleryActions } from '../ducks/mediaGallery';
|
||||||
|
|
||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function SmartAllMedia({ conversationId }: PropsType): JSX.Element {
|
export const SmartAllMedia = memo(function SmartAllMedia({
|
||||||
|
conversationId,
|
||||||
|
}: PropsType) {
|
||||||
const { media, documents } = useSelector(getMediaGalleryState);
|
const { media, documents } = useSelector(getMediaGalleryState);
|
||||||
const { loadMediaItems } = useMediaGalleryActions();
|
const { loadMediaItems } = useMediaGalleryActions();
|
||||||
const { saveAttachment } = useConversationsActions();
|
const { saveAttachment } = useConversationsActions();
|
||||||
|
@ -32,4 +31,4 @@ export function SmartAllMedia({ conversationId }: PropsType): JSX.Element {
|
||||||
saveAttachment={saveAttachment}
|
saveAttachment={saveAttachment}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
import React, { memo } from 'react';
|
||||||
import React from 'react';
|
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import type { VerificationTransport } from '../../types/VerificationTransport';
|
import type { VerificationTransport } from '../../types/VerificationTransport';
|
||||||
import { App } from '../../components/App';
|
import { App } from '../../components/App';
|
||||||
import OS from '../../util/os/osMain';
|
import OS from '../../util/os/osMain';
|
||||||
|
@ -30,13 +28,11 @@ function renderInbox(): JSX.Element {
|
||||||
return <SmartInbox />;
|
return <SmartInbox />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SmartApp(): JSX.Element {
|
export const SmartApp = memo(function SmartApp() {
|
||||||
const app = useSelector((state: StateType) => state.app);
|
const app = useSelector((state: StateType) => state.app);
|
||||||
|
|
||||||
const { openInbox } = useAppActions();
|
const { openInbox } = useAppActions();
|
||||||
|
|
||||||
const { scrollToMessage } = useConversationsActions();
|
const { scrollToMessage } = useConversationsActions();
|
||||||
|
|
||||||
const { viewStory } = useStoriesActions();
|
const { viewStory } = useStoriesActions();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -84,4 +80,4 @@ export function SmartApp(): JSX.Element {
|
||||||
viewStory={viewStory}
|
viewStory={viewStory}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
|
@ -1,23 +1,29 @@
|
||||||
// Copyright 2020 Signal Messenger, LLC
|
// Copyright 2020 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { memoize } from 'lodash';
|
import { memoize } from 'lodash';
|
||||||
import { mapDispatchToProps } from '../actions';
|
import React, { memo } from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
import type {
|
import type {
|
||||||
DirectIncomingCall,
|
DirectIncomingCall,
|
||||||
GroupIncomingCall,
|
GroupIncomingCall,
|
||||||
} from '../../components/CallManager';
|
} from '../../components/CallManager';
|
||||||
import { CallManager } from '../../components/CallManager';
|
import { CallManager } from '../../components/CallManager';
|
||||||
|
import type { SafetyNumberProps } from '../../components/SafetyNumberChangeDialog';
|
||||||
|
import { isConversationTooBigToRing as getIsConversationTooBigToRing } from '../../conversations/isConversationTooBigToRing';
|
||||||
|
import * as log from '../../logging/log';
|
||||||
import { calling as callingService } from '../../services/calling';
|
import { calling as callingService } from '../../services/calling';
|
||||||
import { getIntl, getTheme } from '../selectors/user';
|
import {
|
||||||
import { getMe, getConversationSelector } from '../selectors/conversations';
|
FALLBACK_NOTIFICATION_TITLE,
|
||||||
import { getActiveCall } from '../ducks/calling';
|
NotificationSetting,
|
||||||
import type { ConversationType } from '../ducks/conversations';
|
NotificationType,
|
||||||
import { getCallLinkSelector, getIncomingCall } from '../selectors/calling';
|
notificationService,
|
||||||
import { isGroupCallRaiseHandEnabled } from '../../util/isGroupCallRaiseHandEnabled';
|
} from '../../services/notifications';
|
||||||
import { isGroupCallReactionsEnabled } from '../../util/isGroupCallReactionsEnabled';
|
import {
|
||||||
|
bounceAppIconStart,
|
||||||
|
bounceAppIconStop,
|
||||||
|
} from '../../shims/bounceAppIcon';
|
||||||
|
import type { CallLinkType } from '../../types/CallLink';
|
||||||
import type {
|
import type {
|
||||||
ActiveCallBaseType,
|
ActiveCallBaseType,
|
||||||
ActiveCallType,
|
ActiveCallType,
|
||||||
|
@ -27,32 +33,32 @@ import type {
|
||||||
ConversationsByDemuxIdType,
|
ConversationsByDemuxIdType,
|
||||||
GroupCallRemoteParticipantType,
|
GroupCallRemoteParticipantType,
|
||||||
} from '../../types/Calling';
|
} from '../../types/Calling';
|
||||||
import type { AciString } from '../../types/ServiceId';
|
|
||||||
import { CallMode, CallState } from '../../types/Calling';
|
import { CallMode, CallState } from '../../types/Calling';
|
||||||
import type { CallLinkType } from '../../types/CallLink';
|
import type { AciString } from '../../types/ServiceId';
|
||||||
import type { StateType } from '../reducer';
|
|
||||||
import { missingCaseError } from '../../util/missingCaseError';
|
|
||||||
import { SmartCallingDeviceSelection } from './CallingDeviceSelection';
|
|
||||||
import type { SafetyNumberProps } from '../../components/SafetyNumberChangeDialog';
|
|
||||||
import { SmartSafetyNumberViewer } from './SafetyNumberViewer';
|
|
||||||
import { callingTones } from '../../util/callingTones';
|
|
||||||
import {
|
|
||||||
bounceAppIconStart,
|
|
||||||
bounceAppIconStop,
|
|
||||||
} from '../../shims/bounceAppIcon';
|
|
||||||
import {
|
|
||||||
FALLBACK_NOTIFICATION_TITLE,
|
|
||||||
NotificationSetting,
|
|
||||||
NotificationType,
|
|
||||||
notificationService,
|
|
||||||
} from '../../services/notifications';
|
|
||||||
import * as log from '../../logging/log';
|
|
||||||
import { getPreferredBadgeSelector } from '../selectors/badges';
|
|
||||||
import { isConversationTooBigToRing } from '../../conversations/isConversationTooBigToRing';
|
|
||||||
import { strictAssert } from '../../util/assert';
|
import { strictAssert } from '../../util/assert';
|
||||||
|
import { callLinkToConversation } from '../../util/callLinks';
|
||||||
|
import { callingTones } from '../../util/callingTones';
|
||||||
|
import { isGroupCallRaiseHandEnabled } from '../../util/isGroupCallRaiseHandEnabled';
|
||||||
|
import { isGroupCallReactionsEnabled } from '../../util/isGroupCallReactionsEnabled';
|
||||||
|
import { missingCaseError } from '../../util/missingCaseError';
|
||||||
|
import { useAudioPlayerActions } from '../ducks/audioPlayer';
|
||||||
|
import { getActiveCall, useCallingActions } from '../ducks/calling';
|
||||||
|
import type { ConversationType } from '../ducks/conversations';
|
||||||
|
import { useToastActions } from '../ducks/toast';
|
||||||
|
import type { StateType } from '../reducer';
|
||||||
|
import { getHasInitialLoadCompleted } from '../selectors/app';
|
||||||
|
import { getPreferredBadgeSelector } from '../selectors/badges';
|
||||||
|
import {
|
||||||
|
getAvailableCameras,
|
||||||
|
getCallLinkSelector,
|
||||||
|
getIncomingCall,
|
||||||
|
} from '../selectors/calling';
|
||||||
|
import { getConversationSelector, getMe } from '../selectors/conversations';
|
||||||
|
import { getIntl, getTheme } from '../selectors/user';
|
||||||
|
import { SmartCallingDeviceSelection } from './CallingDeviceSelection';
|
||||||
|
import { SmartSafetyNumberViewer } from './SafetyNumberViewer';
|
||||||
import { renderEmojiPicker } from './renderEmojiPicker';
|
import { renderEmojiPicker } from './renderEmojiPicker';
|
||||||
import { renderReactionPicker } from './renderReactionPicker';
|
import { renderReactionPicker } from './renderReactionPicker';
|
||||||
import { callLinkToConversation } from '../../util/callLinks';
|
|
||||||
|
|
||||||
function renderDeviceSelection(): JSX.Element {
|
function renderDeviceSelection(): JSX.Element {
|
||||||
return <SmartCallingDeviceSelection />;
|
return <SmartCallingDeviceSelection />;
|
||||||
|
@ -321,7 +327,7 @@ const mapStateToActiveCallProp = (
|
||||||
conversationsByDemuxId,
|
conversationsByDemuxId,
|
||||||
deviceCount: peekInfo.deviceCount,
|
deviceCount: peekInfo.deviceCount,
|
||||||
groupMembers,
|
groupMembers,
|
||||||
isConversationTooBigToRing: isConversationTooBigToRing(conversation),
|
isConversationTooBigToRing: getIsConversationTooBigToRing(conversation),
|
||||||
joinState: call.joinState,
|
joinState: call.joinState,
|
||||||
localDemuxId,
|
localDemuxId,
|
||||||
maxDevices: peekInfo.maxDevices,
|
maxDevices: peekInfo.maxDevices,
|
||||||
|
@ -414,37 +420,105 @@ const mapStateToIncomingCallProp = (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state: StateType) => {
|
export const SmartCallManager = memo(function SmartCallManager() {
|
||||||
const incomingCall = mapStateToIncomingCallProp(state);
|
const i18n = useSelector(getIntl);
|
||||||
|
const theme = useSelector(getTheme);
|
||||||
|
const activeCall = useSelector(mapStateToActiveCallProp);
|
||||||
|
const callLink = useSelector(mapStateToCallLinkProp);
|
||||||
|
const incomingCall = useSelector(mapStateToIncomingCallProp);
|
||||||
|
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||||
|
const availableCameras = useSelector(getAvailableCameras);
|
||||||
|
const hasInitialLoadCompleted = useSelector(getHasInitialLoadCompleted);
|
||||||
|
const me = useSelector(getMe);
|
||||||
|
const isConversationTooBigToRing = incomingCall
|
||||||
|
? getIsConversationTooBigToRing(incomingCall.conversation)
|
||||||
|
: false;
|
||||||
|
|
||||||
return {
|
const {
|
||||||
activeCall: mapStateToActiveCallProp(state),
|
changeCallView,
|
||||||
callLink: mapStateToCallLinkProp(state),
|
closeNeedPermissionScreen,
|
||||||
bounceAppIconStart,
|
getPresentingSources,
|
||||||
bounceAppIconStop,
|
cancelCall,
|
||||||
availableCameras: state.calling.availableCameras,
|
keyChangeOk,
|
||||||
getGroupCallVideoFrameSource,
|
startCall,
|
||||||
getPreferredBadge: getPreferredBadgeSelector(state),
|
toggleParticipants,
|
||||||
hasInitialLoadCompleted: state.app.hasInitialLoadCompleted,
|
acceptCall,
|
||||||
i18n: getIntl(state),
|
declineCall,
|
||||||
isGroupCallRaiseHandEnabled: isGroupCallRaiseHandEnabled(),
|
openSystemPreferencesAction,
|
||||||
isGroupCallReactionsEnabled: isGroupCallReactionsEnabled(),
|
sendGroupCallRaiseHand,
|
||||||
incomingCall,
|
sendGroupCallReaction,
|
||||||
me: getMe(state),
|
setGroupCallVideoRequest,
|
||||||
notifyForCall,
|
setIsCallActive,
|
||||||
playRingtone,
|
setLocalAudio,
|
||||||
stopRingtone,
|
setLocalVideo,
|
||||||
renderEmojiPicker,
|
setLocalPreview,
|
||||||
renderReactionPicker,
|
setOutgoingRing,
|
||||||
renderDeviceSelection,
|
setPresenting,
|
||||||
renderSafetyNumberViewer,
|
setRendererCanvas,
|
||||||
theme: getTheme(state),
|
switchToPresentationView,
|
||||||
isConversationTooBigToRing: incomingCall
|
switchFromPresentationView,
|
||||||
? isConversationTooBigToRing(incomingCall.conversation)
|
hangUpActiveCall,
|
||||||
: false,
|
togglePip,
|
||||||
};
|
toggleScreenRecordingPermissionsDialog,
|
||||||
};
|
toggleSettings,
|
||||||
|
} = useCallingActions();
|
||||||
|
const { showToast } = useToastActions();
|
||||||
|
const { pauseVoiceNotePlayer } = useAudioPlayerActions();
|
||||||
|
|
||||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
return (
|
||||||
|
<CallManager
|
||||||
export const SmartCallManager = smart(CallManager);
|
acceptCall={acceptCall}
|
||||||
|
activeCall={activeCall}
|
||||||
|
availableCameras={availableCameras}
|
||||||
|
bounceAppIconStart={bounceAppIconStart}
|
||||||
|
bounceAppIconStop={bounceAppIconStop}
|
||||||
|
callLink={callLink}
|
||||||
|
cancelCall={cancelCall}
|
||||||
|
changeCallView={changeCallView}
|
||||||
|
closeNeedPermissionScreen={closeNeedPermissionScreen}
|
||||||
|
declineCall={declineCall}
|
||||||
|
getGroupCallVideoFrameSource={getGroupCallVideoFrameSource}
|
||||||
|
getPreferredBadge={getPreferredBadge}
|
||||||
|
getPresentingSources={getPresentingSources}
|
||||||
|
hangUpActiveCall={hangUpActiveCall}
|
||||||
|
hasInitialLoadCompleted={hasInitialLoadCompleted}
|
||||||
|
i18n={i18n}
|
||||||
|
incomingCall={incomingCall}
|
||||||
|
isConversationTooBigToRing={isConversationTooBigToRing}
|
||||||
|
isGroupCallRaiseHandEnabled={isGroupCallRaiseHandEnabled()}
|
||||||
|
isGroupCallReactionsEnabled={isGroupCallReactionsEnabled()}
|
||||||
|
keyChangeOk={keyChangeOk}
|
||||||
|
me={me}
|
||||||
|
notifyForCall={notifyForCall}
|
||||||
|
openSystemPreferencesAction={openSystemPreferencesAction}
|
||||||
|
pauseVoiceNotePlayer={pauseVoiceNotePlayer}
|
||||||
|
playRingtone={playRingtone}
|
||||||
|
renderDeviceSelection={renderDeviceSelection}
|
||||||
|
renderEmojiPicker={renderEmojiPicker}
|
||||||
|
renderReactionPicker={renderReactionPicker}
|
||||||
|
renderSafetyNumberViewer={renderSafetyNumberViewer}
|
||||||
|
sendGroupCallRaiseHand={sendGroupCallRaiseHand}
|
||||||
|
sendGroupCallReaction={sendGroupCallReaction}
|
||||||
|
setGroupCallVideoRequest={setGroupCallVideoRequest}
|
||||||
|
setIsCallActive={setIsCallActive}
|
||||||
|
setLocalAudio={setLocalAudio}
|
||||||
|
setLocalPreview={setLocalPreview}
|
||||||
|
setLocalVideo={setLocalVideo}
|
||||||
|
setOutgoingRing={setOutgoingRing}
|
||||||
|
setPresenting={setPresenting}
|
||||||
|
setRendererCanvas={setRendererCanvas}
|
||||||
|
showToast={showToast}
|
||||||
|
startCall={startCall}
|
||||||
|
stopRingtone={stopRingtone}
|
||||||
|
switchFromPresentationView={switchFromPresentationView}
|
||||||
|
switchToPresentationView={switchToPresentationView}
|
||||||
|
theme={theme}
|
||||||
|
toggleParticipants={toggleParticipants}
|
||||||
|
togglePip={togglePip}
|
||||||
|
toggleScreenRecordingPermissionsDialog={
|
||||||
|
toggleScreenRecordingPermissionsDialog
|
||||||
|
}
|
||||||
|
toggleSettings={toggleSettings}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
@ -1,34 +1,42 @@
|
||||||
// Copyright 2020 Signal Messenger, LLC
|
// Copyright 2020 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { connect } from 'react-redux';
|
import React, { memo } from 'react';
|
||||||
import { mapDispatchToProps } from '../actions';
|
import { useSelector } from 'react-redux';
|
||||||
import { CallingDeviceSelection } from '../../components/CallingDeviceSelection';
|
import { CallingDeviceSelection } from '../../components/CallingDeviceSelection';
|
||||||
import type { StateType } from '../reducer';
|
|
||||||
|
|
||||||
import { getIntl } from '../selectors/user';
|
import { getIntl } from '../selectors/user';
|
||||||
|
import {
|
||||||
|
getAvailableCameras,
|
||||||
|
getAvailableMicrophones,
|
||||||
|
getAvailableSpeakers,
|
||||||
|
getSelectedCamera,
|
||||||
|
getSelectedMicrophone,
|
||||||
|
getSelectedSpeaker,
|
||||||
|
} from '../selectors/calling';
|
||||||
|
import { useCallingActions } from '../ducks/calling';
|
||||||
|
|
||||||
const mapStateToProps = (state: StateType) => {
|
export const SmartCallingDeviceSelection = memo(
|
||||||
const {
|
function SmartCallingDeviceSelection() {
|
||||||
availableMicrophones,
|
const i18n = useSelector(getIntl);
|
||||||
availableSpeakers,
|
const availableMicrophones = useSelector(getAvailableMicrophones);
|
||||||
selectedMicrophone,
|
const selectedMicrophone = useSelector(getSelectedMicrophone);
|
||||||
selectedSpeaker,
|
const availableSpeakers = useSelector(getAvailableSpeakers);
|
||||||
availableCameras,
|
const selectedSpeaker = useSelector(getSelectedSpeaker);
|
||||||
selectedCamera,
|
const availableCameras = useSelector(getAvailableCameras);
|
||||||
} = state.calling;
|
const selectedCamera = useSelector(getSelectedCamera);
|
||||||
|
const { changeIODevice, toggleSettings } = useCallingActions();
|
||||||
return {
|
return (
|
||||||
availableCameras,
|
<CallingDeviceSelection
|
||||||
availableMicrophones,
|
availableCameras={availableCameras}
|
||||||
availableSpeakers,
|
availableMicrophones={availableMicrophones}
|
||||||
i18n: getIntl(state),
|
availableSpeakers={availableSpeakers}
|
||||||
selectedCamera,
|
changeIODevice={changeIODevice}
|
||||||
selectedMicrophone,
|
i18n={i18n}
|
||||||
selectedSpeaker,
|
selectedCamera={selectedCamera}
|
||||||
};
|
selectedMicrophone={selectedMicrophone}
|
||||||
};
|
selectedSpeaker={selectedSpeaker}
|
||||||
|
toggleSettings={toggleSettings}
|
||||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
/>
|
||||||
|
);
|
||||||
export const SmartCallingDeviceSelection = smart(CallingDeviceSelection);
|
}
|
||||||
|
);
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
// Copyright 2023 Signal Messenger, LLC
|
// Copyright 2023 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
import React, { memo, useCallback, useEffect } from 'react';
|
||||||
import React, { useCallback, useEffect } from 'react';
|
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { useItemsActions } from '../ducks/items';
|
import { useItemsActions } from '../ducks/items';
|
||||||
import {
|
import {
|
||||||
|
@ -88,7 +87,7 @@ function renderToastManager(props: {
|
||||||
return <SmartToastManager disableMegaphone {...props} />;
|
return <SmartToastManager disableMegaphone {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SmartCallsTab(): JSX.Element {
|
export const SmartCallsTab = memo(function SmartCallsTab() {
|
||||||
const i18n = useSelector(getIntl);
|
const i18n = useSelector(getIntl);
|
||||||
const navTabsCollapsed = useSelector(getNavTabsCollapsed);
|
const navTabsCollapsed = useSelector(getNavTabsCollapsed);
|
||||||
const preferredLeftPaneWidth = useSelector(getPreferredLeftPaneWidth);
|
const preferredLeftPaneWidth = useSelector(getPreferredLeftPaneWidth);
|
||||||
|
@ -185,4 +184,4 @@ export function SmartCallsTab(): JSX.Element {
|
||||||
savePreferredLeftPaneWidth={savePreferredLeftPaneWidth}
|
savePreferredLeftPaneWidth={savePreferredLeftPaneWidth}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
|
@ -1,29 +1,33 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
import React, { memo, useCallback } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { mapDispatchToProps } from '../actions';
|
|
||||||
import { CaptchaDialog } from '../../components/CaptchaDialog';
|
import { CaptchaDialog } from '../../components/CaptchaDialog';
|
||||||
import type { StateType } from '../reducer';
|
|
||||||
import { getIntl } from '../selectors/user';
|
import { getIntl } from '../selectors/user';
|
||||||
import { isChallengePending } from '../selectors/network';
|
import { isChallengePending } from '../selectors/network';
|
||||||
import { getChallengeURL } from '../../challenge';
|
import { getChallengeURL } from '../../challenge';
|
||||||
import * as log from '../../logging/log';
|
import * as log from '../../logging/log';
|
||||||
|
|
||||||
const mapStateToProps = (state: StateType) => {
|
export type SmartCaptchaDialogProps = Readonly<{
|
||||||
return {
|
onSkip: () => void;
|
||||||
...state.updates,
|
}>;
|
||||||
isPending: isChallengePending(state),
|
|
||||||
i18n: getIntl(state),
|
|
||||||
|
|
||||||
onContinue() {
|
export const SmartCaptchaDialog = memo(function SmartCaptchaDialog({
|
||||||
|
onSkip,
|
||||||
|
}: SmartCaptchaDialogProps) {
|
||||||
|
const i18n = useSelector(getIntl);
|
||||||
|
const isPending = useSelector(isChallengePending);
|
||||||
|
const handleContinue = useCallback(() => {
|
||||||
const url = getChallengeURL('chat');
|
const url = getChallengeURL('chat');
|
||||||
log.info(`CaptchaDialog: navigating to ${url}`);
|
log.info(`CaptchaDialog: navigating to ${url}`);
|
||||||
document.location.href = url;
|
document.location.href = url;
|
||||||
},
|
}, []);
|
||||||
};
|
return (
|
||||||
};
|
<CaptchaDialog
|
||||||
|
i18n={i18n}
|
||||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
isPending={isPending}
|
||||||
|
onSkip={onSkip}
|
||||||
export const SmartCaptchaDialog = smart(CaptchaDialog);
|
onContinue={handleContinue}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
@ -1,53 +1,91 @@
|
||||||
// Copyright 2020 Signal Messenger, LLC
|
// Copyright 2020 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
import React, { memo, useCallback } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { mapDispatchToProps } from '../actions';
|
|
||||||
import type { PropsDataType } from '../../components/ChatColorPicker';
|
|
||||||
import { ChatColorPicker } from '../../components/ChatColorPicker';
|
import { ChatColorPicker } from '../../components/ChatColorPicker';
|
||||||
import type { StateType } from '../reducer';
|
|
||||||
import {
|
import {
|
||||||
getConversationSelector,
|
getConversationSelector,
|
||||||
getConversationsWithCustomColorSelector,
|
getConversationsWithCustomColorSelector,
|
||||||
} from '../selectors/conversations';
|
} from '../selectors/conversations';
|
||||||
import { getIntl } from '../selectors/user';
|
import { getIntl } from '../selectors/user';
|
||||||
import { getDefaultConversationColor } from '../selectors/items';
|
import {
|
||||||
|
getCustomColors,
|
||||||
|
getDefaultConversationColor,
|
||||||
|
} from '../selectors/items';
|
||||||
import { getConversationColorAttributes } from '../../util/getConversationColorAttributes';
|
import { getConversationColorAttributes } from '../../util/getConversationColorAttributes';
|
||||||
|
import {
|
||||||
|
useConversationsActions,
|
||||||
|
type ConversationType,
|
||||||
|
} from '../ducks/conversations';
|
||||||
|
import { useItemsActions } from '../ducks/items';
|
||||||
|
|
||||||
export type SmartChatColorPickerProps = {
|
export type SmartChatColorPickerProps = Readonly<{
|
||||||
conversationId?: string;
|
conversationId?: string;
|
||||||
};
|
}>;
|
||||||
|
|
||||||
const mapStateToProps = (
|
export const SmartChatColorPicker = memo(function SmartChatColorPicker({
|
||||||
state: StateType,
|
conversationId,
|
||||||
props: SmartChatColorPickerProps
|
}: SmartChatColorPickerProps) {
|
||||||
): PropsDataType => {
|
const i18n = useSelector(getIntl);
|
||||||
const conversation = props.conversationId
|
const customColors = useSelector(getCustomColors) ?? {};
|
||||||
? getConversationSelector(state)(props.conversationId)
|
const defaultConversationColor = useSelector(getDefaultConversationColor);
|
||||||
|
const conversationSelector = useSelector(getConversationSelector);
|
||||||
|
const conversationWithCustomColorSelector = useSelector(
|
||||||
|
getConversationsWithCustomColorSelector
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
addCustomColor,
|
||||||
|
removeCustomColor,
|
||||||
|
setGlobalDefaultConversationColor,
|
||||||
|
resetDefaultChatColor,
|
||||||
|
editCustomColor,
|
||||||
|
} = useItemsActions();
|
||||||
|
const {
|
||||||
|
colorSelected,
|
||||||
|
resetAllChatColors,
|
||||||
|
removeCustomColorOnConversations,
|
||||||
|
} = useConversationsActions();
|
||||||
|
|
||||||
|
const conversation = conversationId
|
||||||
|
? conversationSelector(conversationId)
|
||||||
: {};
|
: {};
|
||||||
const defaultConversationColor = getDefaultConversationColor(state);
|
|
||||||
const colorValues = getConversationColorAttributes(
|
const colorValues = getConversationColorAttributes(
|
||||||
conversation,
|
conversation,
|
||||||
defaultConversationColor
|
defaultConversationColor
|
||||||
);
|
);
|
||||||
|
|
||||||
const { customColors } = state.items;
|
const selectedColor = colorValues.conversationColor;
|
||||||
|
const selectedCustomColor = {
|
||||||
return {
|
|
||||||
...props,
|
|
||||||
customColors: customColors ? customColors.colors : {},
|
|
||||||
getConversationsWithCustomColor: (colorId: string) =>
|
|
||||||
Promise.resolve(getConversationsWithCustomColorSelector(state)(colorId)),
|
|
||||||
i18n: getIntl(state),
|
|
||||||
selectedColor: colorValues.conversationColor,
|
|
||||||
selectedCustomColor: {
|
|
||||||
id: colorValues.customColorId,
|
id: colorValues.customColorId,
|
||||||
value: colorValues.customColor,
|
value: colorValues.customColor,
|
||||||
},
|
|
||||||
};
|
};
|
||||||
};
|
|
||||||
|
|
||||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
const getConversationsWithCustomColor = useCallback(
|
||||||
|
async (colorId: string): Promise<Array<ConversationType>> => {
|
||||||
|
return conversationWithCustomColorSelector(colorId);
|
||||||
|
},
|
||||||
|
[conversationWithCustomColorSelector]
|
||||||
|
);
|
||||||
|
|
||||||
export const SmartChatColorPicker = smart(ChatColorPicker);
|
return (
|
||||||
|
<ChatColorPicker
|
||||||
|
addCustomColor={addCustomColor}
|
||||||
|
colorSelected={colorSelected}
|
||||||
|
conversationId={conversationId}
|
||||||
|
customColors={customColors}
|
||||||
|
editCustomColor={editCustomColor}
|
||||||
|
getConversationsWithCustomColor={getConversationsWithCustomColor}
|
||||||
|
i18n={i18n}
|
||||||
|
isGlobal={false}
|
||||||
|
removeCustomColor={removeCustomColor}
|
||||||
|
removeCustomColorOnConversations={removeCustomColorOnConversations}
|
||||||
|
resetAllChatColors={resetAllChatColors}
|
||||||
|
resetDefaultChatColor={resetDefaultChatColor}
|
||||||
|
selectedColor={selectedColor}
|
||||||
|
selectedCustomColor={selectedCustomColor}
|
||||||
|
setGlobalDefaultConversationColor={setGlobalDefaultConversationColor}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
// Copyright 2023 Signal Messenger, LLC
|
// Copyright 2023 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
import React, { memo, useEffect, useRef } from 'react';
|
||||||
import React, { useEffect, useRef } from 'react';
|
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { ChatsTab } from '../../components/ChatsTab';
|
import { ChatsTab } from '../../components/ChatsTab';
|
||||||
import { SmartConversationView } from './ConversationView';
|
import { SmartConversationView } from './ConversationView';
|
||||||
|
@ -12,7 +11,6 @@ import { useGlobalModalActions } from '../ducks/globalModals';
|
||||||
import { getIntl } from '../selectors/user';
|
import { getIntl } from '../selectors/user';
|
||||||
import { usePrevious } from '../../hooks/usePrevious';
|
import { usePrevious } from '../../hooks/usePrevious';
|
||||||
import { TargetedMessageSource } from '../ducks/conversationsEnums';
|
import { TargetedMessageSource } from '../ducks/conversationsEnums';
|
||||||
import type { ConversationsStateType } from '../ducks/conversations';
|
|
||||||
import { useConversationsActions } from '../ducks/conversations';
|
import { useConversationsActions } from '../ducks/conversations';
|
||||||
import { useToastActions } from '../ducks/toast';
|
import { useToastActions } from '../ducks/toast';
|
||||||
import type { StateType } from '../reducer';
|
import type { StateType } from '../reducer';
|
||||||
|
@ -36,7 +34,7 @@ function renderMiniPlayer(options: { shouldFlow: boolean }) {
|
||||||
return <SmartMiniPlayer {...options} />;
|
return <SmartMiniPlayer {...options} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SmartChatsTab(): JSX.Element {
|
export const SmartChatsTab = memo(function SmartChatsTab() {
|
||||||
const i18n = useSelector(getIntl);
|
const i18n = useSelector(getIntl);
|
||||||
const navTabsCollapsed = useSelector(getNavTabsCollapsed);
|
const navTabsCollapsed = useSelector(getNavTabsCollapsed);
|
||||||
const hasFailedStorySends = useSelector(getHasAnyFailedStorySends);
|
const hasFailedStorySends = useSelector(getHasAnyFailedStorySends);
|
||||||
|
@ -44,9 +42,7 @@ export function SmartChatsTab(): JSX.Element {
|
||||||
const otherTabsUnreadStats = useSelector(getOtherTabsUnreadStats);
|
const otherTabsUnreadStats = useSelector(getOtherTabsUnreadStats);
|
||||||
|
|
||||||
const { selectedConversationId, targetedMessage, targetedMessageSource } =
|
const { selectedConversationId, targetedMessage, targetedMessageSource } =
|
||||||
useSelector<StateType, ConversationsStateType>(
|
useSelector((state: StateType) => state.conversations);
|
||||||
state => state.conversations
|
|
||||||
);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
onConversationClosed,
|
onConversationClosed,
|
||||||
|
@ -73,13 +69,7 @@ export function SmartChatsTab(): JSX.Element {
|
||||||
) {
|
) {
|
||||||
scrollToMessage(selectedConversationId, targetedMessage);
|
scrollToMessage(selectedConversationId, targetedMessage);
|
||||||
}
|
}
|
||||||
}, [
|
}, [onConversationOpened, selectedConversationId, scrollToMessage, targetedMessage, targetedMessageSource]);
|
||||||
onConversationOpened,
|
|
||||||
selectedConversationId,
|
|
||||||
scrollToMessage,
|
|
||||||
targetedMessage,
|
|
||||||
targetedMessageSource,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const prevConversationId = usePrevious(
|
const prevConversationId = usePrevious(
|
||||||
selectedConversationId,
|
selectedConversationId,
|
||||||
|
@ -157,4 +147,4 @@ export function SmartChatsTab(): JSX.Element {
|
||||||
showWhatsNewModal={showWhatsNewModal}
|
showWhatsNewModal={showWhatsNewModal}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
|
@ -1,26 +1,20 @@
|
||||||
// Copyright 2022 Signal Messenger, LLC
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
import React, { memo, useMemo } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import type { StateType } from '../reducer';
|
|
||||||
import { mapDispatchToProps } from '../actions';
|
|
||||||
import { strictAssert } from '../../util/assert';
|
import { strictAssert } from '../../util/assert';
|
||||||
import { lookupConversationWithoutServiceId } from '../../util/lookupConversationWithoutServiceId';
|
import { lookupConversationWithoutServiceId } from '../../util/lookupConversationWithoutServiceId';
|
||||||
import { getUsernameFromSearch } from '../../util/Username';
|
import { getUsernameFromSearch } from '../../util/Username';
|
||||||
|
|
||||||
import type { StatePropsType } from '../../components/conversation/conversation-details/AddGroupMembersModal/ChooseGroupMembersModal';
|
|
||||||
import { ChooseGroupMembersModal } from '../../components/conversation/conversation-details/AddGroupMembersModal/ChooseGroupMembersModal';
|
import { ChooseGroupMembersModal } from '../../components/conversation/conversation-details/AddGroupMembersModal/ChooseGroupMembersModal';
|
||||||
|
|
||||||
import { getIntl, getTheme, getRegionCode } from '../selectors/user';
|
import { getIntl, getTheme, getRegionCode } from '../selectors/user';
|
||||||
import {
|
import {
|
||||||
getCandidateContactsForNewGroup,
|
getCandidateContactsForNewGroup,
|
||||||
getConversationByIdSelector,
|
getConversationByIdSelector,
|
||||||
getMe,
|
getMe,
|
||||||
} from '../selectors/conversations';
|
} from '../selectors/conversations';
|
||||||
import { getPreferredBadgeSelector } from '../selectors/badges';
|
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||||
|
|
||||||
export type SmartChooseGroupMembersModalPropsType = {
|
export type SmartChooseGroupMembersModalPropsType = Readonly<{
|
||||||
conversationIdsAlreadyInGroup: Set<string>;
|
conversationIdsAlreadyInGroup: Set<string>;
|
||||||
maxGroupSize: number;
|
maxGroupSize: number;
|
||||||
confirmAdds: () => void;
|
confirmAdds: () => void;
|
||||||
|
@ -30,16 +24,28 @@ export type SmartChooseGroupMembersModalPropsType = {
|
||||||
selectedConversationIds: ReadonlyArray<string>;
|
selectedConversationIds: ReadonlyArray<string>;
|
||||||
setSearchTerm: (_: string) => void;
|
setSearchTerm: (_: string) => void;
|
||||||
toggleSelectedContact: (conversationId: string) => void;
|
toggleSelectedContact: (conversationId: string) => void;
|
||||||
};
|
}>;
|
||||||
|
|
||||||
const mapStateToProps = (
|
export const SmartChooseGroupMembersModal = memo(
|
||||||
state: StateType,
|
function SmartChooseGroupMembersModal({
|
||||||
props: SmartChooseGroupMembersModalPropsType
|
conversationIdsAlreadyInGroup,
|
||||||
): StatePropsType => {
|
maxGroupSize,
|
||||||
const conversationSelector = getConversationByIdSelector(state);
|
confirmAdds,
|
||||||
|
onClose,
|
||||||
|
removeSelectedContact,
|
||||||
|
searchTerm,
|
||||||
|
selectedConversationIds,
|
||||||
|
setSearchTerm,
|
||||||
|
toggleSelectedContact,
|
||||||
|
}: SmartChooseGroupMembersModalPropsType) {
|
||||||
|
const i18n = useSelector(getIntl);
|
||||||
|
const theme = useSelector(getTheme);
|
||||||
|
const regionCode = useSelector(getRegionCode);
|
||||||
|
const me = useSelector(getMe);
|
||||||
|
const conversationSelector = useSelector(getConversationByIdSelector);
|
||||||
|
|
||||||
const candidateContacts = getCandidateContactsForNewGroup(state);
|
const candidateContacts = useSelector(getCandidateContactsForNewGroup);
|
||||||
const selectedContacts = props.selectedConversationIds.map(conversationId => {
|
const selectedContacts = selectedConversationIds.map(conversationId => {
|
||||||
const convo = conversationSelector(conversationId);
|
const convo = conversationSelector(conversationId);
|
||||||
strictAssert(
|
strictAssert(
|
||||||
convo,
|
convo,
|
||||||
|
@ -48,23 +54,33 @@ const mapStateToProps = (
|
||||||
return convo;
|
return convo;
|
||||||
});
|
});
|
||||||
|
|
||||||
const { searchTerm } = props;
|
const { showUserNotFoundModal } = useGlobalModalActions();
|
||||||
|
|
||||||
return {
|
const username = useMemo(() => {
|
||||||
...props,
|
return getUsernameFromSearch(searchTerm);
|
||||||
regionCode: getRegionCode(state),
|
}, [searchTerm]);
|
||||||
candidateContacts,
|
|
||||||
getPreferredBadge: getPreferredBadgeSelector(state),
|
|
||||||
i18n: getIntl(state),
|
|
||||||
theme: getTheme(state),
|
|
||||||
ourE164: getMe(state).e164,
|
|
||||||
ourUsername: getMe(state).username,
|
|
||||||
selectedContacts,
|
|
||||||
lookupConversationWithoutServiceId,
|
|
||||||
username: getUsernameFromSearch(searchTerm),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
return (
|
||||||
|
<ChooseGroupMembersModal
|
||||||
export const SmartChooseGroupMembersModal = smart(ChooseGroupMembersModal);
|
regionCode={regionCode}
|
||||||
|
candidateContacts={candidateContacts}
|
||||||
|
confirmAdds={confirmAdds}
|
||||||
|
conversationIdsAlreadyInGroup={conversationIdsAlreadyInGroup}
|
||||||
|
i18n={i18n}
|
||||||
|
maxGroupSize={maxGroupSize}
|
||||||
|
onClose={onClose}
|
||||||
|
ourE164={me.e164}
|
||||||
|
ourUsername={me.username}
|
||||||
|
removeSelectedContact={removeSelectedContact}
|
||||||
|
searchTerm={searchTerm}
|
||||||
|
selectedContacts={selectedContacts}
|
||||||
|
setSearchTerm={setSearchTerm}
|
||||||
|
theme={theme}
|
||||||
|
toggleSelectedContact={toggleSelectedContact}
|
||||||
|
lookupConversationWithoutServiceId={lookupConversationWithoutServiceId}
|
||||||
|
showUserNotFoundModal={showUserNotFoundModal}
|
||||||
|
username={username}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
// Copyright 2024 Signal Messenger, LLC
|
// Copyright 2024 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
import React, { memo, useMemo } from 'react';
|
||||||
import React, { useMemo } from 'react';
|
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { CollidingAvatars } from '../../components/CollidingAvatars';
|
import { CollidingAvatars } from '../../components/CollidingAvatars';
|
||||||
import { getIntl } from '../selectors/user';
|
import { getIntl } from '../selectors/user';
|
||||||
import { getConversationSelector } from '../selectors/conversations';
|
import { getConversationSelector } from '../selectors/conversations';
|
||||||
|
@ -12,9 +10,9 @@ export type PropsType = Readonly<{
|
||||||
conversationIds: ReadonlyArray<string>;
|
conversationIds: ReadonlyArray<string>;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export function SmartCollidingAvatars({
|
export const SmartCollidingAvatars = memo(function SmartCollidingAvatars({
|
||||||
conversationIds,
|
conversationIds,
|
||||||
}: PropsType): JSX.Element {
|
}: PropsType) {
|
||||||
const i18n = useSelector(getIntl);
|
const i18n = useSelector(getIntl);
|
||||||
const getConversation = useSelector(getConversationSelector);
|
const getConversation = useSelector(getConversationSelector);
|
||||||
|
|
||||||
|
@ -25,4 +23,4 @@ export function SmartCollidingAvatars({
|
||||||
}, [conversationIds, getConversation]);
|
}, [conversationIds, getConversation]);
|
||||||
|
|
||||||
return <CollidingAvatars i18n={i18n} conversations={conversations} />;
|
return <CollidingAvatars i18n={i18n} conversations={conversations} />;
|
||||||
}
|
});
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright 2019 Signal Messenger, LLC
|
// Copyright 2019 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React, { useCallback, useMemo } from 'react';
|
import React, { useCallback, useMemo, memo } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { CompositionArea } from '../../components/CompositionArea';
|
import { CompositionArea } from '../../components/CompositionArea';
|
||||||
import { useContactNameData } from '../../components/conversation/ContactName';
|
import { useContactNameData } from '../../components/conversation/ContactName';
|
||||||
|
@ -78,7 +78,11 @@ function renderSmartCompositionRecordingDraft(
|
||||||
return <SmartCompositionRecordingDraft {...draftProps} />;
|
return <SmartCompositionRecordingDraft {...draftProps} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SmartCompositionArea({ id }: { id: string }): JSX.Element {
|
export const SmartCompositionArea = memo(function SmartCompositionArea({
|
||||||
|
id,
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
}) {
|
||||||
const conversationSelector = useSelector(getConversationSelector);
|
const conversationSelector = useSelector(getConversationSelector);
|
||||||
const conversation = conversationSelector(id);
|
const conversation = conversationSelector(id);
|
||||||
strictAssert(conversation, `Conversation id ${id} not found!`);
|
strictAssert(conversation, `Conversation id ${id} not found!`);
|
||||||
|
@ -346,4 +350,4 @@ export function SmartCompositionArea({ id }: { id: string }): JSX.Element {
|
||||||
showConversation={showConversation}
|
showConversation={showConversation}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright 2022 Signal Messenger, LLC
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React, { useCallback } from 'react';
|
import React, { memo, useCallback } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { CompositionRecording } from '../../components/CompositionRecording';
|
import { CompositionRecording } from '../../components/CompositionRecording';
|
||||||
import { mapDispatchToProps } from '../actions';
|
import { mapDispatchToProps } from '../actions';
|
||||||
|
@ -15,9 +15,10 @@ export type SmartCompositionRecordingProps = {
|
||||||
onBeforeSend: () => void;
|
onBeforeSend: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function SmartCompositionRecording({
|
export const SmartCompositionRecording = memo(
|
||||||
|
function SmartCompositionRecording({
|
||||||
onBeforeSend,
|
onBeforeSend,
|
||||||
}: SmartCompositionRecordingProps): JSX.Element | null {
|
}: SmartCompositionRecordingProps) {
|
||||||
const i18n = useSelector(getIntl);
|
const i18n = useSelector(getIntl);
|
||||||
const selectedConversationId = useSelector(getSelectedConversationId);
|
const selectedConversationId = useSelector(getSelectedConversationId);
|
||||||
const { cancelRecording, completeRecording } = useAudioRecorderActions();
|
const { cancelRecording, completeRecording } = useAudioRecorderActions();
|
||||||
|
@ -33,7 +34,9 @@ export function SmartCompositionRecording({
|
||||||
if (selectedConversationId) {
|
if (selectedConversationId) {
|
||||||
completeRecording(selectedConversationId, voiceNoteAttachment => {
|
completeRecording(selectedConversationId, voiceNoteAttachment => {
|
||||||
onBeforeSend();
|
onBeforeSend();
|
||||||
sendMultiMediaMessage(selectedConversationId, { voiceNoteAttachment });
|
sendMultiMediaMessage(selectedConversationId, {
|
||||||
|
voiceNoteAttachment,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
|
@ -60,4 +63,5 @@ export function SmartCompositionRecording({
|
||||||
hideToast={hideToast}
|
hideToast={hideToast}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright 2023 Signal Messenger, LLC
|
// Copyright 2023 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React, { useCallback } from 'react';
|
import React, { memo, useCallback } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { CompositionRecordingDraft } from '../../components/CompositionRecordingDraft';
|
import { CompositionRecordingDraft } from '../../components/CompositionRecordingDraft';
|
||||||
import type { AttachmentDraftType } from '../../types/Attachment';
|
import type { AttachmentDraftType } from '../../types/Attachment';
|
||||||
|
@ -21,9 +21,10 @@ export type SmartCompositionRecordingDraftProps = {
|
||||||
voiceNoteAttachment: AttachmentDraftType;
|
voiceNoteAttachment: AttachmentDraftType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function SmartCompositionRecordingDraft({
|
export const SmartCompositionRecordingDraft = memo(
|
||||||
|
function SmartCompositionRecordingDraft({
|
||||||
voiceNoteAttachment,
|
voiceNoteAttachment,
|
||||||
}: SmartCompositionRecordingDraftProps): JSX.Element {
|
}: SmartCompositionRecordingDraftProps) {
|
||||||
const i18n = useSelector(getIntl);
|
const i18n = useSelector(getIntl);
|
||||||
const active = useSelector(selectAudioPlayerActive);
|
const active = useSelector(selectAudioPlayerActive);
|
||||||
const selectedConversationId = useSelector(getSelectedConversationId);
|
const selectedConversationId = useSelector(getSelectedConversationId);
|
||||||
|
@ -153,4 +154,5 @@ export function SmartCompositionRecordingDraft({
|
||||||
onScrub={handleScrub}
|
onScrub={handleScrub}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
// Copyright 2022 Signal Messenger, LLC
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
import React, { memo } from 'react';
|
||||||
import React from 'react';
|
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import type { CompositionTextAreaProps } from '../../components/CompositionTextArea';
|
import type { CompositionTextAreaProps } from '../../components/CompositionTextArea';
|
||||||
import { CompositionTextArea } from '../../components/CompositionTextArea';
|
import { CompositionTextArea } from '../../components/CompositionTextArea';
|
||||||
|
@ -26,9 +25,9 @@ export type SmartCompositionTextAreaProps = Pick<
|
||||||
| 'scrollerRef'
|
| 'scrollerRef'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function SmartCompositionTextArea(
|
export const SmartCompositionTextArea = memo(function SmartCompositionTextArea(
|
||||||
props: SmartCompositionTextAreaProps
|
props: SmartCompositionTextAreaProps
|
||||||
): JSX.Element {
|
) {
|
||||||
const i18n = useSelector(getIntl);
|
const i18n = useSelector(getIntl);
|
||||||
const platform = useSelector(getPlatform);
|
const platform = useSelector(getPlatform);
|
||||||
|
|
||||||
|
@ -51,4 +50,4 @@ export function SmartCompositionTextArea(
|
||||||
platform={platform}
|
platform={platform}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
|
@ -1,16 +1,10 @@
|
||||||
// Copyright 2022 Signal Messenger, LLC
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
import React, { memo } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import type { StateType } from '../reducer';
|
|
||||||
import { mapDispatchToProps } from '../actions';
|
|
||||||
import { strictAssert } from '../../util/assert';
|
import { strictAssert } from '../../util/assert';
|
||||||
|
|
||||||
import type { StatePropsType } from '../../components/conversation/conversation-details/AddGroupMembersModal/ConfirmAdditionsModal';
|
|
||||||
import { ConfirmAdditionsModal } from '../../components/conversation/conversation-details/AddGroupMembersModal/ConfirmAdditionsModal';
|
import { ConfirmAdditionsModal } from '../../components/conversation/conversation-details/AddGroupMembersModal/ConfirmAdditionsModal';
|
||||||
import type { RequestState } from '../../components/conversation/conversation-details/util';
|
import type { RequestState } from '../../components/conversation/conversation-details/util';
|
||||||
|
|
||||||
import { getIntl } from '../selectors/user';
|
import { getIntl } from '../selectors/user';
|
||||||
import { getConversationByIdSelector } from '../selectors/conversations';
|
import { getConversationByIdSelector } from '../selectors/conversations';
|
||||||
|
|
||||||
|
@ -22,13 +16,18 @@ export type SmartConfirmAdditionsModalPropsType = {
|
||||||
requestState: RequestState;
|
requestState: RequestState;
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (
|
export const SmartConfirmAdditionsModal = memo(
|
||||||
state: StateType,
|
function SmartConfirmAdditionsModal({
|
||||||
props: SmartConfirmAdditionsModalPropsType
|
selectedConversationIds,
|
||||||
): StatePropsType => {
|
groupTitle,
|
||||||
const conversationSelector = getConversationByIdSelector(state);
|
makeRequest,
|
||||||
|
onClose,
|
||||||
|
requestState,
|
||||||
|
}: SmartConfirmAdditionsModalPropsType) {
|
||||||
|
const i18n = useSelector(getIntl);
|
||||||
|
const conversationSelector = useSelector(getConversationByIdSelector);
|
||||||
|
|
||||||
const selectedContacts = props.selectedConversationIds.map(conversationId => {
|
const selectedContacts = selectedConversationIds.map(conversationId => {
|
||||||
const convo = conversationSelector(conversationId);
|
const convo = conversationSelector(conversationId);
|
||||||
strictAssert(
|
strictAssert(
|
||||||
convo,
|
convo,
|
||||||
|
@ -37,13 +36,15 @@ const mapStateToProps = (
|
||||||
return convo;
|
return convo;
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return (
|
||||||
...props,
|
<ConfirmAdditionsModal
|
||||||
selectedContacts,
|
i18n={i18n}
|
||||||
i18n: getIntl(state),
|
selectedContacts={selectedContacts}
|
||||||
};
|
groupTitle={groupTitle}
|
||||||
};
|
makeRequest={makeRequest}
|
||||||
|
onClose={onClose}
|
||||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
requestState={requestState}
|
||||||
|
/>
|
||||||
export const SmartConfirmAdditionsModal = smart(ConfirmAdditionsModal);
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
|
@ -1,59 +1,92 @@
|
||||||
// Copyright 2020 Signal Messenger, LLC
|
// Copyright 2020 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { connect } from 'react-redux';
|
import React, { memo, useMemo } from 'react';
|
||||||
import { mapDispatchToProps } from '../actions';
|
import { useSelector } from 'react-redux';
|
||||||
import type { PropsDataType } from '../../components/conversation/ContactModal';
|
|
||||||
import { ContactModal } from '../../components/conversation/ContactModal';
|
import { ContactModal } from '../../components/conversation/ContactModal';
|
||||||
import type { StateType } from '../reducer';
|
|
||||||
|
|
||||||
import { getAreWeASubscriber } from '../selectors/items';
|
import { getAreWeASubscriber } from '../selectors/items';
|
||||||
import { getIntl, getTheme } from '../selectors/user';
|
import { getIntl, getTheme } from '../selectors/user';
|
||||||
import { getBadgesSelector } from '../selectors/badges';
|
import { getBadgesSelector } from '../selectors/badges';
|
||||||
import { getConversationSelector } from '../selectors/conversations';
|
import { getConversationSelector } from '../selectors/conversations';
|
||||||
import { getHasStoriesSelector } from '../selectors/stories2';
|
import { getHasStoriesSelector } from '../selectors/stories2';
|
||||||
import { getActiveCallState } from '../selectors/calling';
|
import { getActiveCallState } from '../selectors/calling';
|
||||||
|
import { useStoriesActions } from '../ducks/stories';
|
||||||
|
import { useConversationsActions } from '../ducks/conversations';
|
||||||
|
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||||
|
import { useCallingActions } from '../ducks/calling';
|
||||||
|
import { getContactModalState } from '../selectors/globalModals';
|
||||||
|
|
||||||
const mapStateToProps = (state: StateType): PropsDataType => {
|
export const SmartContactModal = memo(function SmartContactModal() {
|
||||||
const { contactId, conversationId } =
|
const i18n = useSelector(getIntl);
|
||||||
state.globalModals.contactModalState || {};
|
const theme = useSelector(getTheme);
|
||||||
|
const { conversationId, contactId } = useSelector(getContactModalState) ?? {};
|
||||||
|
const conversationSelector = useSelector(getConversationSelector);
|
||||||
|
const hasStoriesSelector = useSelector(getHasStoriesSelector);
|
||||||
|
const activeCallState = useSelector(getActiveCallState);
|
||||||
|
const badgesSelector = useSelector(getBadgesSelector);
|
||||||
|
const areWeASubscriber = useSelector(getAreWeASubscriber);
|
||||||
|
|
||||||
const currentConversation = getConversationSelector(state)(conversationId);
|
const conversation = conversationSelector(conversationId);
|
||||||
const contact = getConversationSelector(state)(contactId);
|
const contact = conversationSelector(contactId);
|
||||||
|
const hasStories = hasStoriesSelector(contactId);
|
||||||
|
const hasActiveCall = activeCallState != null;
|
||||||
|
const badges = badgesSelector(contact.badges);
|
||||||
|
|
||||||
const areWeAdmin =
|
const areWeAdmin = conversation?.areWeAdmin ?? false;
|
||||||
currentConversation && currentConversation.areWeAdmin
|
|
||||||
? currentConversation.areWeAdmin
|
|
||||||
: false;
|
|
||||||
|
|
||||||
let isMember = false;
|
const ourMembership = useMemo(() => {
|
||||||
let isAdmin = false;
|
return conversation?.memberships?.find(membership => {
|
||||||
if (contact && currentConversation && currentConversation.memberships) {
|
return membership.aci === contact.serviceId;
|
||||||
currentConversation.memberships.forEach(membership => {
|
|
||||||
if (membership.aci === contact.serviceId) {
|
|
||||||
isMember = true;
|
|
||||||
isAdmin = membership.isAdmin;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}, [conversation?.memberships, contact]);
|
||||||
|
|
||||||
const hasStories = getHasStoriesSelector(state)(contactId);
|
const isMember = ourMembership != null;
|
||||||
|
const isAdmin = ourMembership?.isAdmin ?? false;
|
||||||
|
|
||||||
return {
|
const {
|
||||||
areWeASubscriber: getAreWeASubscriber(state),
|
removeMemberFromGroup,
|
||||||
areWeAdmin,
|
showConversation,
|
||||||
badges: getBadgesSelector(state)(contact.badges),
|
updateConversationModelSharedGroups,
|
||||||
hasActiveCall: Boolean(getActiveCallState(state)),
|
toggleAdmin,
|
||||||
contact,
|
blockConversation,
|
||||||
conversation: currentConversation,
|
} = useConversationsActions();
|
||||||
hasStories,
|
const { viewUserStories } = useStoriesActions();
|
||||||
i18n: getIntl(state),
|
const {
|
||||||
isAdmin,
|
toggleAboutContactModal,
|
||||||
isMember,
|
toggleAddUserToAnotherGroupModal,
|
||||||
theme: getTheme(state),
|
toggleSafetyNumberModal,
|
||||||
};
|
hideContactModal,
|
||||||
};
|
} = useGlobalModalActions();
|
||||||
|
const {
|
||||||
|
onOutgoingVideoCallInConversation,
|
||||||
|
onOutgoingAudioCallInConversation,
|
||||||
|
} = useCallingActions();
|
||||||
|
|
||||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
return (
|
||||||
|
<ContactModal
|
||||||
export const SmartContactModal = smart(ContactModal);
|
areWeAdmin={areWeAdmin}
|
||||||
|
areWeASubscriber={areWeASubscriber}
|
||||||
|
badges={badges}
|
||||||
|
blockConversation={blockConversation}
|
||||||
|
contact={contact}
|
||||||
|
conversation={conversation}
|
||||||
|
hasActiveCall={hasActiveCall}
|
||||||
|
hasStories={hasStories}
|
||||||
|
hideContactModal={hideContactModal}
|
||||||
|
i18n={i18n}
|
||||||
|
isAdmin={isAdmin}
|
||||||
|
isMember={isMember}
|
||||||
|
onOutgoingAudioCallInConversation={onOutgoingAudioCallInConversation}
|
||||||
|
onOutgoingVideoCallInConversation={onOutgoingVideoCallInConversation}
|
||||||
|
removeMemberFromGroup={removeMemberFromGroup}
|
||||||
|
showConversation={showConversation}
|
||||||
|
theme={theme}
|
||||||
|
toggleAboutContactModal={toggleAboutContactModal}
|
||||||
|
toggleAddUserToAnotherGroupModal={toggleAddUserToAnotherGroupModal}
|
||||||
|
toggleAdmin={toggleAdmin}
|
||||||
|
toggleSafetyNumberModal={toggleSafetyNumberModal}
|
||||||
|
updateConversationModelSharedGroups={updateConversationModelSharedGroups}
|
||||||
|
viewUserStories={viewUserStories}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
@ -1,32 +1,25 @@
|
||||||
// Copyright 2020 Signal Messenger, LLC
|
// Copyright 2020 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
import React, { memo } from 'react';
|
||||||
import * as React from 'react';
|
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import type { StateType } from '../reducer';
|
|
||||||
|
|
||||||
import { ContactName } from '../../components/conversation/ContactName';
|
import { ContactName } from '../../components/conversation/ContactName';
|
||||||
|
|
||||||
import { getIntl } from '../selectors/user';
|
import { getIntl } from '../selectors/user';
|
||||||
import type { GetConversationByIdType } from '../selectors/conversations';
|
|
||||||
import {
|
import {
|
||||||
getConversationSelector,
|
getConversationSelector,
|
||||||
getSelectedConversationId,
|
getSelectedConversationId,
|
||||||
} from '../selectors/conversations';
|
} from '../selectors/conversations';
|
||||||
|
|
||||||
import type { LocalizerType } from '../../types/Util';
|
|
||||||
import { useGlobalModalActions } from '../ducks/globalModals';
|
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||||
|
|
||||||
type ExternalProps = {
|
type ExternalProps = {
|
||||||
contactId: string;
|
contactId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function SmartContactName(props: ExternalProps): JSX.Element {
|
export const SmartContactName = memo(function SmartContactName(
|
||||||
|
props: ExternalProps
|
||||||
|
) {
|
||||||
const { contactId } = props;
|
const { contactId } = props;
|
||||||
const i18n = useSelector<StateType, LocalizerType>(getIntl);
|
const i18n = useSelector(getIntl);
|
||||||
const getConversation = useSelector<StateType, GetConversationByIdType>(
|
const getConversation = useSelector(getConversationSelector);
|
||||||
getConversationSelector
|
|
||||||
);
|
|
||||||
|
|
||||||
const contact = getConversation(contactId) || {
|
const contact = getConversation(contactId) || {
|
||||||
title: i18n('icu:unknownContact'),
|
title: i18n('icu:unknownContact'),
|
||||||
|
@ -43,4 +36,4 @@ export function SmartContactName(props: ExternalProps): JSX.Element {
|
||||||
onClick={() => showContactModal(contact.id, currentConversation.id)}
|
onClick={() => showContactModal(contact.id, currentConversation.id)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
// Copyright 2022 Signal Messenger, LLC
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React, { useCallback } from 'react';
|
import React, { memo, useCallback } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { mapValues } from 'lodash';
|
import { mapValues } from 'lodash';
|
||||||
import type { StateType } from '../reducer';
|
import type { StateType } from '../reducer';
|
||||||
|
|
||||||
import { ContactSpoofingReviewDialog } from '../../components/conversation/ContactSpoofingReviewDialog';
|
import { ContactSpoofingReviewDialog } from '../../components/conversation/ContactSpoofingReviewDialog';
|
||||||
|
|
||||||
import { useConversationsActions } from '../ducks/conversations';
|
import { useConversationsActions } from '../ducks/conversations';
|
||||||
import type { GetConversationByIdType } from '../selectors/conversations';
|
|
||||||
import {
|
import {
|
||||||
getConversationSelector,
|
getConversationSelector,
|
||||||
getConversationByServiceIdSelector,
|
getConversationByServiceIdSelector,
|
||||||
|
@ -33,14 +30,11 @@ export type PropsType = Readonly<{
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export function SmartContactSpoofingReviewDialog(
|
export const SmartContactSpoofingReviewDialog = memo(
|
||||||
props: PropsType
|
function SmartContactSpoofingReviewDialog(props: PropsType) {
|
||||||
): JSX.Element | null {
|
|
||||||
const { conversationId } = props;
|
const { conversationId } = props;
|
||||||
|
|
||||||
const getConversation = useSelector<StateType, GetConversationByIdType>(
|
const getConversation = useSelector(getConversationSelector);
|
||||||
getConversationSelector
|
|
||||||
);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
acceptConversation,
|
acceptConversation,
|
||||||
|
@ -145,4 +139,5 @@ export function SmartContactSpoofingReviewDialog(
|
||||||
safe={safe}
|
safe={safe}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
|
@ -1,43 +1,45 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { sortBy } from 'lodash';
|
import { sortBy } from 'lodash';
|
||||||
|
import React, { memo } from 'react';
|
||||||
import type { StateType } from '../reducer';
|
import { useSelector } from 'react-redux';
|
||||||
import { mapDispatchToProps } from '../actions';
|
|
||||||
import type { StateProps } from '../../components/conversation/conversation-details/ConversationDetails';
|
|
||||||
import { ConversationDetails } from '../../components/conversation/conversation-details/ConversationDetails';
|
import { ConversationDetails } from '../../components/conversation/conversation-details/ConversationDetails';
|
||||||
import {
|
import {
|
||||||
getConversationByIdSelector,
|
getGroupSizeHardLimit,
|
||||||
getConversationByServiceIdSelector,
|
getGroupSizeRecommendedLimit,
|
||||||
getAllComposableConversations,
|
} from '../../groups/limits';
|
||||||
} from '../selectors/conversations';
|
import { SignalService as Proto } from '../../protobuf';
|
||||||
|
import type { CallHistoryGroup } from '../../types/CallDisposition';
|
||||||
|
import { assertDev } from '../../util/assert';
|
||||||
|
import { getConversationColorAttributes } from '../../util/getConversationColorAttributes';
|
||||||
import { getGroupMemberships } from '../../util/getGroupMemberships';
|
import { getGroupMemberships } from '../../util/getGroupMemberships';
|
||||||
import { getActiveCallState } from '../selectors/calling';
|
|
||||||
import {
|
|
||||||
getAreWeASubscriber,
|
|
||||||
getDefaultConversationColor,
|
|
||||||
} from '../selectors/items';
|
|
||||||
import { getIntl, getTheme } from '../selectors/user';
|
|
||||||
import {
|
import {
|
||||||
getBadgesSelector,
|
getBadgesSelector,
|
||||||
getPreferredBadgeSelector,
|
getPreferredBadgeSelector,
|
||||||
} from '../selectors/badges';
|
} from '../selectors/badges';
|
||||||
import { assertDev } from '../../util/assert';
|
import { getActiveCallState } from '../selectors/calling';
|
||||||
import { SignalService as Proto } from '../../protobuf';
|
import {
|
||||||
import { getConversationColorAttributes } from '../../util/getConversationColorAttributes';
|
getAllComposableConversations,
|
||||||
|
getConversationByIdSelector,
|
||||||
|
getConversationByServiceIdSelector,
|
||||||
|
} from '../selectors/conversations';
|
||||||
|
import {
|
||||||
|
getAreWeASubscriber,
|
||||||
|
getDefaultConversationColor,
|
||||||
|
} from '../selectors/items';
|
||||||
|
import { getSelectedNavTab } from '../selectors/nav';
|
||||||
|
import { getIntl, getTheme } from '../selectors/user';
|
||||||
import type { SmartChooseGroupMembersModalPropsType } from './ChooseGroupMembersModal';
|
import type { SmartChooseGroupMembersModalPropsType } from './ChooseGroupMembersModal';
|
||||||
import { SmartChooseGroupMembersModal } from './ChooseGroupMembersModal';
|
import { SmartChooseGroupMembersModal } from './ChooseGroupMembersModal';
|
||||||
import type { SmartConfirmAdditionsModalPropsType } from './ConfirmAdditionsModal';
|
import type { SmartConfirmAdditionsModalPropsType } from './ConfirmAdditionsModal';
|
||||||
import { SmartConfirmAdditionsModal } from './ConfirmAdditionsModal';
|
import { SmartConfirmAdditionsModal } from './ConfirmAdditionsModal';
|
||||||
import {
|
import type { ConversationType } from '../ducks/conversations';
|
||||||
getGroupSizeRecommendedLimit,
|
import { useConversationsActions } from '../ducks/conversations';
|
||||||
getGroupSizeHardLimit,
|
import { useCallingActions } from '../ducks/calling';
|
||||||
} from '../../groups/limits';
|
import { useSearchActions } from '../ducks/search';
|
||||||
import type { CallHistoryGroup } from '../../types/CallDisposition';
|
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||||
import { getSelectedNavTab } from '../selectors/nav';
|
import { useLightboxActions } from '../ducks/lightbox';
|
||||||
|
|
||||||
export type SmartConversationDetailsProps = {
|
export type SmartConversationDetailsProps = {
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
|
@ -58,79 +60,155 @@ const renderConfirmAdditionsModal = (
|
||||||
return <SmartConfirmAdditionsModal {...props} />;
|
return <SmartConfirmAdditionsModal {...props} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (
|
function getGroupsInCommonSorted(
|
||||||
state: StateType,
|
conversation: ConversationType,
|
||||||
props: SmartConversationDetailsProps
|
allComposableConversations: ReadonlyArray<ConversationType>
|
||||||
): StateProps => {
|
) {
|
||||||
const conversationSelector = getConversationByIdSelector(state);
|
if (conversation.type === 'direct') {
|
||||||
const conversation = conversationSelector(props.conversationId);
|
return [];
|
||||||
|
}
|
||||||
|
const groupsInCommonUnsorted = allComposableConversations.filter(
|
||||||
|
otherConversation => {
|
||||||
|
if (otherConversation.type !== 'group') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return otherConversation.memberships?.some(member => {
|
||||||
|
return member.aci === conversation.serviceId;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return sortBy(groupsInCommonUnsorted, 'title');
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SmartConversationDetails = memo(function SmartConversationDetails({
|
||||||
|
conversationId,
|
||||||
|
callHistoryGroup,
|
||||||
|
}: SmartConversationDetailsProps) {
|
||||||
|
const i18n = useSelector(getIntl);
|
||||||
|
const theme = useSelector(getTheme);
|
||||||
|
const activeCall = useSelector(getActiveCallState);
|
||||||
|
const allComposableConversations = useSelector(getAllComposableConversations);
|
||||||
|
const areWeASubscriber = useSelector(getAreWeASubscriber);
|
||||||
|
const badgesSelector = useSelector(getBadgesSelector);
|
||||||
|
const conversationByServiceIdSelector = useSelector(
|
||||||
|
getConversationByServiceIdSelector
|
||||||
|
);
|
||||||
|
const conversationSelector = useSelector(getConversationByIdSelector);
|
||||||
|
const defaultConversationColor = useSelector(getDefaultConversationColor);
|
||||||
|
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||||
|
const selectedNavTab = useSelector(getSelectedNavTab);
|
||||||
|
|
||||||
|
const {
|
||||||
|
acceptConversation,
|
||||||
|
addMembersToGroup,
|
||||||
|
blockConversation,
|
||||||
|
deleteAvatarFromDisk,
|
||||||
|
getProfilesForConversation,
|
||||||
|
leaveGroup,
|
||||||
|
loadRecentMediaItems,
|
||||||
|
pushPanelForConversation,
|
||||||
|
replaceAvatar,
|
||||||
|
saveAvatarToDisk,
|
||||||
|
setDisappearingMessages,
|
||||||
|
setMuteExpiration,
|
||||||
|
showConversation,
|
||||||
|
updateGroupAttributes,
|
||||||
|
} = useConversationsActions();
|
||||||
|
const {
|
||||||
|
onOutgoingAudioCallInConversation,
|
||||||
|
onOutgoingVideoCallInConversation,
|
||||||
|
} = useCallingActions();
|
||||||
|
const { searchInConversation } = useSearchActions();
|
||||||
|
const {
|
||||||
|
showContactModal,
|
||||||
|
toggleAboutContactModal,
|
||||||
|
toggleAddUserToAnotherGroupModal,
|
||||||
|
toggleSafetyNumberModal,
|
||||||
|
} = useGlobalModalActions();
|
||||||
|
const { showLightboxWithMedia } = useLightboxActions();
|
||||||
|
|
||||||
|
const conversation = conversationSelector(conversationId);
|
||||||
assertDev(
|
assertDev(
|
||||||
conversation,
|
conversation,
|
||||||
'<SmartConversationDetails> expected a conversation to be found'
|
'<SmartConversationDetails> expected a conversation to be found'
|
||||||
);
|
);
|
||||||
|
const conversationWithColorAttributes = {
|
||||||
|
...conversation,
|
||||||
|
...getConversationColorAttributes(conversation, defaultConversationColor),
|
||||||
|
};
|
||||||
|
|
||||||
const canEditGroupInfo = Boolean(conversation.canEditGroupInfo);
|
|
||||||
const canAddNewMembers = Boolean(conversation.canAddNewMembers);
|
|
||||||
const isAdmin = Boolean(conversation.areWeAdmin);
|
|
||||||
|
|
||||||
const hasGroupLink =
|
|
||||||
Boolean(conversation.groupLink) &&
|
|
||||||
conversation.accessControlAddFromInviteLink !== ACCESS_ENUM.UNSATISFIABLE;
|
|
||||||
|
|
||||||
const conversationByServiceIdSelector =
|
|
||||||
getConversationByServiceIdSelector(state);
|
|
||||||
const groupMemberships = getGroupMemberships(
|
const groupMemberships = getGroupMemberships(
|
||||||
conversation,
|
conversation,
|
||||||
conversationByServiceIdSelector
|
conversationByServiceIdSelector
|
||||||
);
|
);
|
||||||
|
|
||||||
const badges = getBadgesSelector(state)(conversation.badges);
|
const { memberships, pendingApprovalMemberships, pendingMemberships } =
|
||||||
const defaultConversationColor = getDefaultConversationColor(state);
|
groupMemberships;
|
||||||
|
const badges = badgesSelector(conversation.badges);
|
||||||
const groupsInCommon =
|
const canAddNewMembers = conversation.canAddNewMembers ?? false;
|
||||||
conversation.type === 'direct'
|
const canEditGroupInfo = conversation.canEditGroupInfo ?? false;
|
||||||
? getAllComposableConversations(state).filter(
|
const groupsInCommon = getGroupsInCommonSorted(
|
||||||
c =>
|
conversation,
|
||||||
c.type === 'group' &&
|
allComposableConversations
|
||||||
(c.memberships ?? []).some(
|
);
|
||||||
member => member.aci === conversation.serviceId
|
const hasActiveCall = activeCall != null;
|
||||||
)
|
const hasGroupLink =
|
||||||
)
|
conversation.groupLink != null &&
|
||||||
: [];
|
conversation.accessControlAddFromInviteLink !== ACCESS_ENUM.UNSATISFIABLE;
|
||||||
|
const isAdmin = conversation.areWeAdmin ?? false;
|
||||||
const groupsInCommonSorted = sortBy(groupsInCommon, 'title');
|
const isGroup = conversation.type === 'group';
|
||||||
|
|
||||||
const maxGroupSize = getGroupSizeHardLimit(1001);
|
const maxGroupSize = getGroupSizeHardLimit(1001);
|
||||||
const maxRecommendedGroupSize = getGroupSizeRecommendedLimit(151);
|
const maxRecommendedGroupSize = getGroupSizeRecommendedLimit(151);
|
||||||
return {
|
const userAvatarData = conversation.avatars ?? [];
|
||||||
...props,
|
|
||||||
|
|
||||||
areWeASubscriber: getAreWeASubscriber(state),
|
return (
|
||||||
badges,
|
<ConversationDetails
|
||||||
canEditGroupInfo,
|
acceptConversation={acceptConversation}
|
||||||
canAddNewMembers,
|
addMembersToGroup={addMembersToGroup}
|
||||||
conversation: {
|
areWeASubscriber={areWeASubscriber}
|
||||||
...conversation,
|
badges={badges}
|
||||||
...getConversationColorAttributes(conversation, defaultConversationColor),
|
blockConversation={blockConversation}
|
||||||
},
|
callHistoryGroup={callHistoryGroup}
|
||||||
getPreferredBadge: getPreferredBadgeSelector(state),
|
canAddNewMembers={canAddNewMembers}
|
||||||
hasActiveCall: Boolean(getActiveCallState(state)),
|
canEditGroupInfo={canEditGroupInfo}
|
||||||
i18n: getIntl(state),
|
conversation={conversationWithColorAttributes}
|
||||||
isAdmin,
|
deleteAvatarFromDisk={deleteAvatarFromDisk}
|
||||||
...groupMemberships,
|
getPreferredBadge={getPreferredBadge}
|
||||||
maxGroupSize,
|
getProfilesForConversation={getProfilesForConversation}
|
||||||
maxRecommendedGroupSize,
|
groupsInCommon={groupsInCommon}
|
||||||
userAvatarData: conversation.avatars || [],
|
hasActiveCall={hasActiveCall}
|
||||||
hasGroupLink,
|
hasGroupLink={hasGroupLink}
|
||||||
groupsInCommon: groupsInCommonSorted,
|
i18n={i18n}
|
||||||
isGroup: conversation.type === 'group',
|
isAdmin={isAdmin}
|
||||||
selectedNavTab: getSelectedNavTab(state),
|
isGroup={isGroup}
|
||||||
theme: getTheme(state),
|
leaveGroup={leaveGroup}
|
||||||
renderChooseGroupMembersModal,
|
loadRecentMediaItems={loadRecentMediaItems}
|
||||||
renderConfirmAdditionsModal,
|
maxGroupSize={maxGroupSize}
|
||||||
};
|
maxRecommendedGroupSize={maxRecommendedGroupSize}
|
||||||
};
|
memberships={memberships}
|
||||||
|
onOutgoingAudioCallInConversation={onOutgoingAudioCallInConversation}
|
||||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
onOutgoingVideoCallInConversation={onOutgoingVideoCallInConversation}
|
||||||
|
pendingApprovalMemberships={pendingApprovalMemberships}
|
||||||
export const SmartConversationDetails = smart(ConversationDetails);
|
pendingMemberships={pendingMemberships}
|
||||||
|
pushPanelForConversation={pushPanelForConversation}
|
||||||
|
renderChooseGroupMembersModal={renderChooseGroupMembersModal}
|
||||||
|
renderConfirmAdditionsModal={renderConfirmAdditionsModal}
|
||||||
|
replaceAvatar={replaceAvatar}
|
||||||
|
saveAvatarToDisk={saveAvatarToDisk}
|
||||||
|
searchInConversation={searchInConversation}
|
||||||
|
selectedNavTab={selectedNavTab}
|
||||||
|
setDisappearingMessages={setDisappearingMessages}
|
||||||
|
setMuteExpiration={setMuteExpiration}
|
||||||
|
showContactModal={showContactModal}
|
||||||
|
showConversation={showConversation}
|
||||||
|
showLightboxWithMedia={showLightboxWithMedia}
|
||||||
|
theme={theme}
|
||||||
|
toggleAboutContactModal={toggleAboutContactModal}
|
||||||
|
toggleAddUserToAnotherGroupModal={toggleAddUserToAnotherGroupModal}
|
||||||
|
toggleSafetyNumberModal={toggleSafetyNumberModal}
|
||||||
|
updateGroupAttributes={updateGroupAttributes}
|
||||||
|
userAvatarData={userAvatarData}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright 2020 Signal Messenger, LLC
|
// Copyright 2020 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React, { useMemo } from 'react';
|
import React, { memo, useMemo } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { pick } from 'lodash';
|
import { pick } from 'lodash';
|
||||||
import type { ConversationType } from '../ducks/conversations';
|
import type { ConversationType } from '../ducks/conversations';
|
||||||
|
@ -78,7 +78,9 @@ const getOutgoingCallButtonStyle = (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export function SmartConversationHeader({ id }: OwnProps): JSX.Element {
|
export const SmartConversationHeader = memo(function SmartConversationHeader({
|
||||||
|
id,
|
||||||
|
}: OwnProps) {
|
||||||
const conversationSelector = useSelector(getConversationSelector);
|
const conversationSelector = useSelector(getConversationSelector);
|
||||||
const conversation = conversationSelector(id);
|
const conversation = conversationSelector(id);
|
||||||
if (!conversation) {
|
if (!conversation) {
|
||||||
|
@ -91,11 +93,10 @@ export function SmartConversationHeader({ id }: OwnProps): JSX.Element {
|
||||||
const badgeSelector = useSelector(getPreferredBadgeSelector);
|
const badgeSelector = useSelector(getPreferredBadgeSelector);
|
||||||
const badge = badgeSelector(conversation.badges);
|
const badge = badgeSelector(conversation.badges);
|
||||||
const i18n = useSelector(getIntl);
|
const i18n = useSelector(getIntl);
|
||||||
const hasPanelShowing = useSelector<StateType, boolean>(getHasPanelOpen);
|
const hasPanelShowing = useSelector(getHasPanelOpen);
|
||||||
const outgoingCallButtonStyle = useSelector<
|
const outgoingCallButtonStyle = useSelector((state: StateType) => {
|
||||||
StateType,
|
return getOutgoingCallButtonStyle(conversation, state);
|
||||||
OutgoingCallButtonStyle
|
});
|
||||||
>(state => getOutgoingCallButtonStyle(conversation, state));
|
|
||||||
const theme = useSelector(getTheme);
|
const theme = useSelector(getTheme);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
@ -216,4 +217,4 @@ export function SmartConversationHeader({ id }: OwnProps): JSX.Element {
|
||||||
deleteConversation={deleteConversation}
|
deleteConversation={deleteConversation}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
|
@ -1,38 +1,43 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { connect } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
import React, { memo } from 'react';
|
||||||
import { ConversationNotificationsSettings } from '../../components/conversation/conversation-details/ConversationNotificationsSettings';
|
import { ConversationNotificationsSettings } from '../../components/conversation/conversation-details/ConversationNotificationsSettings';
|
||||||
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';
|
import { useConversationsActions } from '../ducks/conversations';
|
||||||
|
|
||||||
export type OwnProps = {
|
export type SmartConversationNotificationsSettingsProps = {
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state: StateType, props: OwnProps) => {
|
export const SmartConversationNotificationsSettings = memo(
|
||||||
const { conversationId } = props;
|
function SmartConversationNotificationsSettings({
|
||||||
|
conversationId,
|
||||||
const conversationSelector = getConversationByIdSelector(state);
|
}: SmartConversationNotificationsSettingsProps) {
|
||||||
|
const i18n = useSelector(getIntl);
|
||||||
|
const conversationSelector = useSelector(getConversationByIdSelector);
|
||||||
|
const { setMuteExpiration, setDontNotifyForMentionsIfMuted } =
|
||||||
|
useConversationsActions();
|
||||||
const conversation = conversationSelector(conversationId);
|
const conversation = conversationSelector(conversationId);
|
||||||
strictAssert(conversation, 'Expected a conversation to be found');
|
strictAssert(conversation, 'Expected a conversation to be found');
|
||||||
|
const {
|
||||||
return {
|
type: conversationType,
|
||||||
id: conversationId,
|
dontNotifyForMentionsIfMuted,
|
||||||
conversationType: conversation.type,
|
muteExpiresAt,
|
||||||
dontNotifyForMentionsIfMuted: Boolean(
|
} = conversation;
|
||||||
conversation.dontNotifyForMentionsIfMuted
|
return (
|
||||||
),
|
<ConversationNotificationsSettings
|
||||||
i18n: getIntl(state),
|
id={conversationId}
|
||||||
muteExpiresAt: conversation.muteExpiresAt,
|
conversationType={conversationType}
|
||||||
};
|
dontNotifyForMentionsIfMuted={dontNotifyForMentionsIfMuted ?? false}
|
||||||
};
|
i18n={i18n}
|
||||||
|
muteExpiresAt={muteExpiresAt}
|
||||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
setMuteExpiration={setMuteExpiration}
|
||||||
|
setDontNotifyForMentionsIfMuted={setDontNotifyForMentionsIfMuted}
|
||||||
export const SmartConversationNotificationsSettings = smart(
|
/>
|
||||||
ConversationNotificationsSettings
|
);
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
import type { MutableRefObject } from 'react';
|
import type { MutableRefObject } from 'react';
|
||||||
import React, {
|
import React, {
|
||||||
forwardRef,
|
forwardRef,
|
||||||
|
memo,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useRef,
|
useRef,
|
||||||
|
@ -91,11 +92,11 @@ function doAnimate({
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ConversationPanel({
|
export const ConversationPanel = memo(function ConversationPanel({
|
||||||
conversationId,
|
conversationId,
|
||||||
}: {
|
}: {
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
}): JSX.Element | null {
|
}) {
|
||||||
const panelInformation = useSelector(getPanelInformation);
|
const panelInformation = useSelector(getPanelInformation);
|
||||||
const { panelAnimationDone, panelAnimationStarted } =
|
const { panelAnimationDone, panelAnimationStarted } =
|
||||||
useConversationsActions();
|
useConversationsActions();
|
||||||
|
@ -250,7 +251,7 @@ export function ConversationPanel({
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
});
|
||||||
|
|
||||||
type PanelPropsType = {
|
type PanelPropsType = {
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React from 'react';
|
import React, { memo, useCallback } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import type { StateType } from '../reducer';
|
import type { StateType } from '../reducer';
|
||||||
import { ConversationPanel } from './ConversationPanel';
|
import { ConversationPanel } from './ConversationPanel';
|
||||||
|
@ -18,7 +18,24 @@ import {
|
||||||
import { useComposerActions } from '../ducks/composer';
|
import { useComposerActions } from '../ducks/composer';
|
||||||
import { useConversationsActions } from '../ducks/conversations';
|
import { useConversationsActions } from '../ducks/conversations';
|
||||||
|
|
||||||
export function SmartConversationView(): JSX.Element {
|
function renderCompositionArea(conversationId: string) {
|
||||||
|
return <SmartCompositionArea id={conversationId} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderConversationHeader(conversationId: string) {
|
||||||
|
return <SmartConversationHeader id={conversationId} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderTimeline(conversationId: string) {
|
||||||
|
return <SmartTimeline key={conversationId} id={conversationId} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPanel(conversationId: string) {
|
||||||
|
return <ConversationPanel conversationId={conversationId} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SmartConversationView = memo(
|
||||||
|
function SmartConversationView(): JSX.Element {
|
||||||
const conversationId = useSelector(getSelectedConversationId);
|
const conversationId = useSelector(getSelectedConversationId);
|
||||||
|
|
||||||
if (!conversationId) {
|
if (!conversationId) {
|
||||||
|
@ -45,24 +62,23 @@ export function SmartConversationView(): JSX.Element {
|
||||||
return activePanel && !isAnimating;
|
return activePanel && !isAnimating;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const onExitSelectMode = useCallback(() => {
|
||||||
|
toggleSelectMode(false);
|
||||||
|
}, [toggleSelectMode]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConversationView
|
<ConversationView
|
||||||
conversationId={conversationId}
|
conversationId={conversationId}
|
||||||
hasOpenModal={hasOpenModal}
|
hasOpenModal={hasOpenModal}
|
||||||
isSelectMode={isSelectMode}
|
isSelectMode={isSelectMode}
|
||||||
onExitSelectMode={() => {
|
onExitSelectMode={onExitSelectMode}
|
||||||
toggleSelectMode(false);
|
|
||||||
}}
|
|
||||||
processAttachments={processAttachments}
|
processAttachments={processAttachments}
|
||||||
renderCompositionArea={() => <SmartCompositionArea id={conversationId} />}
|
renderCompositionArea={renderCompositionArea}
|
||||||
renderConversationHeader={() => (
|
renderConversationHeader={renderConversationHeader}
|
||||||
<SmartConversationHeader id={conversationId} />
|
renderTimeline={renderTimeline}
|
||||||
)}
|
renderPanel={renderPanel}
|
||||||
renderTimeline={() => (
|
|
||||||
<SmartTimeline key={conversationId} id={conversationId} />
|
|
||||||
)}
|
|
||||||
renderPanel={() => <ConversationPanel conversationId={conversationId} />}
|
|
||||||
shouldHideConversationView={shouldHideConversationView}
|
shouldHideConversationView={shouldHideConversationView}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
|
@ -1,19 +1,23 @@
|
||||||
// Copyright 2022 Signal Messenger, LLC
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
import { connect } from 'react-redux';
|
import React, { memo } from 'react';
|
||||||
import { mapDispatchToProps } from '../actions';
|
|
||||||
import { CrashReportDialog } from '../../components/CrashReportDialog';
|
import { CrashReportDialog } from '../../components/CrashReportDialog';
|
||||||
import type { StateType } from '../reducer';
|
|
||||||
import { getIntl } from '../selectors/user';
|
import { getIntl } from '../selectors/user';
|
||||||
|
import { useCrashReportsActions } from '../ducks/crashReports';
|
||||||
|
import { getCrashReportsIsPending } from '../selectors/crashReports';
|
||||||
|
|
||||||
const mapStateToProps = (state: StateType) => {
|
export const SmartCrashReportDialog = memo(function SmartCrashReportDialog() {
|
||||||
return {
|
const i18n = useSelector(getIntl);
|
||||||
...state.crashReports,
|
const isPending = useSelector(getCrashReportsIsPending);
|
||||||
i18n: getIntl(state),
|
const { writeCrashReportsToLog, eraseCrashReports } =
|
||||||
};
|
useCrashReportsActions();
|
||||||
};
|
return (
|
||||||
|
<CrashReportDialog
|
||||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
i18n={i18n}
|
||||||
|
isPending={isPending}
|
||||||
export const SmartCrashReportDialog = smart(CrashReportDialog);
|
writeCrashReportsToLog={writeCrashReportsToLog}
|
||||||
|
eraseCrashReports={eraseCrashReports}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
@ -1,51 +1,66 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
import React, { memo } from 'react';
|
||||||
import * as React from 'react';
|
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import type { StateType } from '../reducer';
|
|
||||||
import type { LocalizerType } from '../../types/Util';
|
|
||||||
import { usePreferredReactionsActions } from '../ducks/preferredReactions';
|
import { usePreferredReactionsActions } from '../ducks/preferredReactions';
|
||||||
import { useItemsActions } from '../ducks/items';
|
import { useItemsActions } from '../ducks/items';
|
||||||
import { getIntl } from '../selectors/user';
|
import { getIntl } from '../selectors/user';
|
||||||
import { getEmojiSkinTone } from '../selectors/items';
|
import { getEmojiSkinTone } from '../selectors/items';
|
||||||
import { useRecentEmojis } from '../selectors/emojis';
|
import { useRecentEmojis } from '../selectors/emojis';
|
||||||
import { getCustomizeModalState } from '../selectors/preferredReactions';
|
import { getCustomizeModalState } from '../selectors/preferredReactions';
|
||||||
|
|
||||||
import { CustomizingPreferredReactionsModal } from '../../components/CustomizingPreferredReactionsModal';
|
import { CustomizingPreferredReactionsModal } from '../../components/CustomizingPreferredReactionsModal';
|
||||||
|
import { strictAssert } from '../../util/assert';
|
||||||
|
|
||||||
export function SmartCustomizingPreferredReactionsModal(): JSX.Element {
|
export const SmartCustomizingPreferredReactionsModal = memo(
|
||||||
const preferredReactionsActions = usePreferredReactionsActions();
|
function SmartCustomizingPreferredReactionsModal(): JSX.Element {
|
||||||
const { onSetSkinTone } = useItemsActions();
|
const i18n = useSelector(getIntl);
|
||||||
|
const customizeModalState = useSelector(getCustomizeModalState);
|
||||||
const i18n = useSelector<StateType, LocalizerType>(getIntl);
|
const skinTone = useSelector(getEmojiSkinTone);
|
||||||
|
|
||||||
const customizeModalState = useSelector<
|
|
||||||
StateType,
|
|
||||||
ReturnType<typeof getCustomizeModalState>
|
|
||||||
>(state => getCustomizeModalState(state));
|
|
||||||
|
|
||||||
const recentEmojis = useRecentEmojis();
|
const recentEmojis = useRecentEmojis();
|
||||||
|
|
||||||
const skinTone = useSelector<StateType, number>(state =>
|
const {
|
||||||
getEmojiSkinTone(state)
|
cancelCustomizePreferredReactionsModal,
|
||||||
);
|
deselectDraftEmoji,
|
||||||
|
replaceSelectedDraftEmoji,
|
||||||
|
resetDraftEmoji,
|
||||||
|
savePreferredReactions,
|
||||||
|
selectDraftEmojiToBeReplaced,
|
||||||
|
} = usePreferredReactionsActions();
|
||||||
|
const { onSetSkinTone } = useItemsActions();
|
||||||
|
|
||||||
if (!customizeModalState) {
|
strictAssert(
|
||||||
throw new Error(
|
customizeModalState != null,
|
||||||
'<SmartCustomizingPreferredReactionsModal> requires a modal'
|
'<SmartCustomizingPreferredReactionsModal> requires a modal'
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
const {
|
||||||
|
hadSaveError,
|
||||||
|
isSaving,
|
||||||
|
draftPreferredReactions,
|
||||||
|
originalPreferredReactions,
|
||||||
|
selectedDraftEmojiIndex,
|
||||||
|
} = customizeModalState;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CustomizingPreferredReactionsModal
|
<CustomizingPreferredReactionsModal
|
||||||
|
cancelCustomizePreferredReactionsModal={
|
||||||
|
cancelCustomizePreferredReactionsModal
|
||||||
|
}
|
||||||
|
deselectDraftEmoji={deselectDraftEmoji}
|
||||||
|
draftPreferredReactions={draftPreferredReactions}
|
||||||
|
hadSaveError={hadSaveError}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
|
isSaving={isSaving}
|
||||||
onSetSkinTone={onSetSkinTone}
|
onSetSkinTone={onSetSkinTone}
|
||||||
|
originalPreferredReactions={originalPreferredReactions}
|
||||||
recentEmojis={recentEmojis}
|
recentEmojis={recentEmojis}
|
||||||
|
replaceSelectedDraftEmoji={replaceSelectedDraftEmoji}
|
||||||
|
resetDraftEmoji={resetDraftEmoji}
|
||||||
|
savePreferredReactions={savePreferredReactions}
|
||||||
|
selectDraftEmojiToBeReplaced={selectDraftEmojiToBeReplaced}
|
||||||
|
selectedDraftEmojiIndex={selectedDraftEmojiIndex}
|
||||||
skinTone={skinTone}
|
skinTone={skinTone}
|
||||||
{...preferredReactionsActions}
|
|
||||||
{...customizeModalState}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
// Copyright 2023 Signal Messenger, LLC
|
// Copyright 2023 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React from 'react';
|
import React, { memo } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import type { DeleteMessagesPropsType } from '../ducks/globalModals';
|
|
||||||
import type { StateType } from '../reducer';
|
import type { StateType } from '../reducer';
|
||||||
import { getIntl } from '../selectors/user';
|
import { getIntl } from '../selectors/user';
|
||||||
import { useGlobalModalActions } from '../ducks/globalModals';
|
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||||
|
@ -12,13 +11,15 @@ import { strictAssert } from '../../util/assert';
|
||||||
import { canDeleteMessagesForEveryone } from '../selectors/message';
|
import { canDeleteMessagesForEveryone } from '../selectors/message';
|
||||||
import { useConversationsActions } from '../ducks/conversations';
|
import { useConversationsActions } from '../ducks/conversations';
|
||||||
import { useToastActions } from '../ducks/toast';
|
import { useToastActions } from '../ducks/toast';
|
||||||
import { getConversationSelector } from '../selectors/conversations';
|
import {
|
||||||
|
getConversationSelector,
|
||||||
|
getLastSelectedMessage,
|
||||||
|
} from '../selectors/conversations';
|
||||||
|
import { getDeleteMessagesProps } from '../selectors/globalModals';
|
||||||
|
|
||||||
export function SmartDeleteMessagesModal(): JSX.Element | null {
|
export const SmartDeleteMessagesModal = memo(
|
||||||
const deleteMessagesProps = useSelector<
|
function SmartDeleteMessagesModal() {
|
||||||
StateType,
|
const deleteMessagesProps = useSelector(getDeleteMessagesProps);
|
||||||
DeleteMessagesPropsType | undefined
|
|
||||||
>(state => state.globalModals.deleteMessagesProps);
|
|
||||||
strictAssert(
|
strictAssert(
|
||||||
deleteMessagesProps != null,
|
deleteMessagesProps != null,
|
||||||
'Cannot render delete messages modal without messages'
|
'Cannot render delete messages modal without messages'
|
||||||
|
@ -31,9 +32,7 @@ export function SmartDeleteMessagesModal(): JSX.Element | null {
|
||||||
const canDeleteForEveryone = useSelector((state: StateType) => {
|
const canDeleteForEveryone = useSelector((state: StateType) => {
|
||||||
return canDeleteMessagesForEveryone(state, { messageIds, isMe });
|
return canDeleteMessagesForEveryone(state, { messageIds, isMe });
|
||||||
});
|
});
|
||||||
const lastSelectedMessage = useSelector((state: StateType) => {
|
const lastSelectedMessage = useSelector(getLastSelectedMessage);
|
||||||
return state.conversations.lastSelectedMessage;
|
|
||||||
});
|
|
||||||
const i18n = useSelector(getIntl);
|
const i18n = useSelector(getIntl);
|
||||||
const { toggleDeleteMessagesModal } = useGlobalModalActions();
|
const { toggleDeleteMessagesModal } = useGlobalModalActions();
|
||||||
const { deleteMessages, deleteMessagesForEveryone } =
|
const { deleteMessages, deleteMessagesForEveryone } =
|
||||||
|
@ -64,4 +63,5 @@ export function SmartDeleteMessagesModal(): JSX.Element | null {
|
||||||
showToast={showToast}
|
showToast={showToast}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
// Copyright 2023 Signal Messenger, LLC
|
// Copyright 2023 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React, { useMemo } from 'react';
|
import React, { memo, useMemo } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import type { GlobalModalsStateType } from '../ducks/globalModals';
|
|
||||||
import type { MessageAttributesType } from '../../model-types.d';
|
import type { MessageAttributesType } from '../../model-types.d';
|
||||||
import type { StateType } from '../reducer';
|
|
||||||
import { EditHistoryMessagesModal } from '../../components/EditHistoryMessagesModal';
|
import { EditHistoryMessagesModal } from '../../components/EditHistoryMessagesModal';
|
||||||
import { getIntl, getPlatform } from '../selectors/user';
|
import { getIntl, getPlatform } from '../selectors/user';
|
||||||
import { getMessagePropsSelector } from '../selectors/message';
|
import { getMessagePropsSelector } from '../selectors/message';
|
||||||
|
@ -14,23 +12,19 @@ import { useConversationsActions } from '../ducks/conversations';
|
||||||
import { useGlobalModalActions } from '../ducks/globalModals';
|
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||||
import { useLightboxActions } from '../ducks/lightbox';
|
import { useLightboxActions } from '../ducks/lightbox';
|
||||||
import { strictAssert } from '../../util/assert';
|
import { strictAssert } from '../../util/assert';
|
||||||
|
import { getEditHistoryMessages } from '../selectors/globalModals';
|
||||||
|
|
||||||
export function SmartEditHistoryMessagesModal(): JSX.Element {
|
export const SmartEditHistoryMessagesModal = memo(
|
||||||
|
function SmartEditHistoryMessagesModal(): JSX.Element {
|
||||||
const i18n = useSelector(getIntl);
|
const i18n = useSelector(getIntl);
|
||||||
const platform = useSelector(getPlatform);
|
const platform = useSelector(getPlatform);
|
||||||
|
|
||||||
const { closeEditHistoryModal } = useGlobalModalActions();
|
const { closeEditHistoryModal } = useGlobalModalActions();
|
||||||
|
|
||||||
const { kickOffAttachmentDownload } = useConversationsActions();
|
const { kickOffAttachmentDownload } = useConversationsActions();
|
||||||
const { showLightbox } = useLightboxActions();
|
const { showLightbox } = useLightboxActions();
|
||||||
|
|
||||||
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||||
|
const messagesAttributes = useSelector(getEditHistoryMessages);
|
||||||
const { editHistoryMessages: messagesAttributes } = useSelector<
|
|
||||||
StateType,
|
|
||||||
GlobalModalsStateType
|
|
||||||
>(state => state.globalModals);
|
|
||||||
|
|
||||||
const messagePropsSelector = useSelector(getMessagePropsSelector);
|
const messagePropsSelector = useSelector(getMessagePropsSelector);
|
||||||
|
|
||||||
strictAssert(messagesAttributes, 'messages not provided');
|
strictAssert(messagesAttributes, 'messages not provided');
|
||||||
|
@ -59,4 +53,5 @@ export function SmartEditHistoryMessagesModal(): JSX.Element {
|
||||||
showLightbox={showLightbox}
|
showLightbox={showLightbox}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
|
@ -1,14 +1,9 @@
|
||||||
// Copyright 2022 Signal Messenger, LLC
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
import React, { memo } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { mapDispatchToProps } from '../actions';
|
|
||||||
|
|
||||||
import type { PropsDataType } from '../../components/EditUsernameModalBody';
|
|
||||||
import { EditUsernameModalBody } from '../../components/EditUsernameModalBody';
|
import { EditUsernameModalBody } from '../../components/EditUsernameModalBody';
|
||||||
import { getMinNickname, getMaxNickname } from '../../util/Username';
|
import { getMinNickname, getMaxNickname } from '../../util/Username';
|
||||||
|
|
||||||
import type { StateType } from '../reducer';
|
|
||||||
import { getIntl } from '../selectors/user';
|
import { getIntl } from '../selectors/user';
|
||||||
import {
|
import {
|
||||||
getUsernameReservationState,
|
getUsernameReservationState,
|
||||||
|
@ -18,25 +13,55 @@ import {
|
||||||
} from '../selectors/username';
|
} from '../selectors/username';
|
||||||
import { getUsernameCorrupted } from '../selectors/items';
|
import { getUsernameCorrupted } from '../selectors/items';
|
||||||
import { getMe } from '../selectors/conversations';
|
import { getMe } from '../selectors/conversations';
|
||||||
|
import { useUsernameActions } from '../ducks/username';
|
||||||
|
import { useToastActions } from '../ducks/toast';
|
||||||
|
|
||||||
function mapStateToProps(state: StateType): PropsDataType {
|
export type SmartEditUsernameModalBodyProps = Readonly<{
|
||||||
const i18n = getIntl(state);
|
isRootModal: boolean;
|
||||||
const { username } = getMe(state);
|
onClose(): void;
|
||||||
const usernameCorrupted = getUsernameCorrupted(state);
|
}>;
|
||||||
|
|
||||||
return {
|
export const SmartEditUsernameModalBody = memo(
|
||||||
i18n,
|
function SmartEditUsernameModalBody({
|
||||||
usernameCorrupted,
|
isRootModal,
|
||||||
currentUsername: usernameCorrupted ? undefined : username,
|
onClose,
|
||||||
minNickname: getMinNickname(),
|
}: SmartEditUsernameModalBodyProps) {
|
||||||
maxNickname: getMaxNickname(),
|
const i18n = useSelector(getIntl);
|
||||||
state: getUsernameReservationState(state),
|
const { username } = useSelector(getMe);
|
||||||
recoveredUsername: getRecoveredUsername(state),
|
const usernameCorrupted = useSelector(getUsernameCorrupted);
|
||||||
reservation: getUsernameReservationObject(state),
|
const currentUsername = usernameCorrupted ? undefined : username;
|
||||||
error: getUsernameReservationError(state),
|
const minNickname = getMinNickname();
|
||||||
};
|
const maxNickname = getMaxNickname();
|
||||||
}
|
const state = useSelector(getUsernameReservationState);
|
||||||
|
const recoveredUsername = useSelector(getRecoveredUsername);
|
||||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
const reservation = useSelector(getUsernameReservationObject);
|
||||||
|
const error = useSelector(getUsernameReservationError);
|
||||||
export const SmartEditUsernameModalBody = smart(EditUsernameModalBody);
|
const {
|
||||||
|
setUsernameReservationError,
|
||||||
|
clearUsernameReservation,
|
||||||
|
reserveUsername,
|
||||||
|
confirmUsername,
|
||||||
|
} = useUsernameActions();
|
||||||
|
const { showToast } = useToastActions();
|
||||||
|
return (
|
||||||
|
<EditUsernameModalBody
|
||||||
|
i18n={i18n}
|
||||||
|
usernameCorrupted={usernameCorrupted}
|
||||||
|
currentUsername={currentUsername}
|
||||||
|
minNickname={minNickname}
|
||||||
|
maxNickname={maxNickname}
|
||||||
|
state={state}
|
||||||
|
recoveredUsername={recoveredUsername}
|
||||||
|
reservation={reservation}
|
||||||
|
error={error}
|
||||||
|
setUsernameReservationError={setUsernameReservationError}
|
||||||
|
clearUsernameReservation={clearUsernameReservation}
|
||||||
|
reserveUsername={reserveUsername}
|
||||||
|
confirmUsername={confirmUsername}
|
||||||
|
showToast={showToast}
|
||||||
|
isRootModal={isRootModal}
|
||||||
|
onClose={onClose}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
|
@ -1,32 +1,28 @@
|
||||||
// Copyright 2020 Signal Messenger, LLC
|
// Copyright 2020 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import * as React from 'react';
|
import React, { forwardRef, memo } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import type { StateType } from '../reducer';
|
|
||||||
import { useRecentEmojis } from '../selectors/emojis';
|
import { useRecentEmojis } from '../selectors/emojis';
|
||||||
import { useEmojisActions as useEmojiActions } from '../ducks/emojis';
|
import { useEmojisActions as useEmojiActions } from '../ducks/emojis';
|
||||||
|
|
||||||
import type { Props as EmojiPickerProps } from '../../components/emoji/EmojiPicker';
|
import type { Props as EmojiPickerProps } from '../../components/emoji/EmojiPicker';
|
||||||
import { EmojiPicker } from '../../components/emoji/EmojiPicker';
|
import { EmojiPicker } from '../../components/emoji/EmojiPicker';
|
||||||
import { getIntl } from '../selectors/user';
|
import { getIntl } from '../selectors/user';
|
||||||
import { getEmojiSkinTone } from '../selectors/items';
|
import { getEmojiSkinTone } from '../selectors/items';
|
||||||
import type { LocalizerType } from '../../types/Util';
|
|
||||||
|
|
||||||
export const SmartEmojiPicker = React.forwardRef<
|
export const SmartEmojiPicker = memo(
|
||||||
|
forwardRef<
|
||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
Pick<
|
Pick<
|
||||||
EmojiPickerProps,
|
EmojiPickerProps,
|
||||||
'onClickSettings' | 'onPickEmoji' | 'onSetSkinTone' | 'onClose' | 'style'
|
'onClickSettings' | 'onPickEmoji' | 'onSetSkinTone' | 'onClose' | 'style'
|
||||||
>
|
>
|
||||||
>(function SmartEmojiPickerInner(
|
>(function SmartEmojiPickerInner(
|
||||||
{ onClickSettings, onPickEmoji, onSetSkinTone, onClose, style },
|
{ onClickSettings, onPickEmoji, onSetSkinTone, onClose, style },
|
||||||
ref
|
ref
|
||||||
) {
|
) {
|
||||||
const i18n = useSelector<StateType, LocalizerType>(getIntl);
|
const i18n = useSelector(getIntl);
|
||||||
const skinTone = useSelector<StateType, number>(state =>
|
const skinTone = useSelector(getEmojiSkinTone);
|
||||||
getEmojiSkinTone(state)
|
|
||||||
);
|
|
||||||
|
|
||||||
const recentEmojis = useRecentEmojis();
|
const recentEmojis = useRecentEmojis();
|
||||||
|
|
||||||
|
@ -53,4 +49,5 @@ export const SmartEmojiPicker = React.forwardRef<
|
||||||
style={style}
|
style={style}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
})
|
||||||
|
);
|
||||||
|
|
|
@ -7,7 +7,6 @@ import type {
|
||||||
ForwardMessagePropsType,
|
ForwardMessagePropsType,
|
||||||
ForwardMessagesPropsType,
|
ForwardMessagesPropsType,
|
||||||
} from '../ducks/globalModals';
|
} from '../ducks/globalModals';
|
||||||
import type { StateType } from '../reducer';
|
|
||||||
import * as log from '../../logging/log';
|
import * as log from '../../logging/log';
|
||||||
import { ForwardMessagesModal } from '../../components/ForwardMessagesModal';
|
import { ForwardMessagesModal } from '../../components/ForwardMessagesModal';
|
||||||
import { LinkPreviewSourceType } from '../../types/LinkPreview';
|
import { LinkPreviewSourceType } from '../../types/LinkPreview';
|
||||||
|
@ -37,6 +36,7 @@ import type {
|
||||||
ForwardMessageData,
|
ForwardMessageData,
|
||||||
MessageForwardDraft,
|
MessageForwardDraft,
|
||||||
} from '../../types/ForwardDraft';
|
} from '../../types/ForwardDraft';
|
||||||
|
import { getForwardMessagesProps } from '../selectors/globalModals';
|
||||||
|
|
||||||
function toMessageForwardDraft(
|
function toMessageForwardDraft(
|
||||||
props: ForwardMessagePropsType,
|
props: ForwardMessagePropsType,
|
||||||
|
@ -54,10 +54,7 @@ function toMessageForwardDraft(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SmartForwardMessagesModal(): JSX.Element | null {
|
export function SmartForwardMessagesModal(): JSX.Element | null {
|
||||||
const forwardMessagesProps = useSelector<
|
const forwardMessagesProps = useSelector(getForwardMessagesProps);
|
||||||
StateType,
|
|
||||||
ForwardMessagesPropsType | undefined
|
|
||||||
>(state => state.globalModals.forwardMessagesProps);
|
|
||||||
|
|
||||||
if (forwardMessagesProps == null) {
|
if (forwardMessagesProps == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright 2022 Signal Messenger, LLC
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React from 'react';
|
import React, { memo } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { ConversationDetailsMembershipList } from '../../components/conversation/conversation-details/ConversationDetailsMembershipList';
|
import { ConversationDetailsMembershipList } from '../../components/conversation/conversation-details/ConversationDetailsMembershipList';
|
||||||
|
@ -19,7 +19,9 @@ export type PropsType = {
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function SmartGV1Members({ conversationId }: PropsType): JSX.Element {
|
export const SmartGV1Members = memo(function SmartGV1Members({
|
||||||
|
conversationId,
|
||||||
|
}: PropsType): JSX.Element {
|
||||||
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||||
const i18n = useSelector(getIntl);
|
const i18n = useSelector(getIntl);
|
||||||
const theme = useSelector(getTheme);
|
const theme = useSelector(getTheme);
|
||||||
|
@ -53,4 +55,4 @@ export function SmartGV1Members({ conversationId }: PropsType): JSX.Element {
|
||||||
theme={theme}
|
theme={theme}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React, { useCallback } from 'react';
|
import React, { memo, useCallback } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import type { GlobalModalsStateType } from '../ducks/globalModals';
|
|
||||||
import type { StateType } from '../reducer';
|
|
||||||
import type { ButtonVariant } from '../../components/Button';
|
import type { ButtonVariant } from '../../components/Button';
|
||||||
import { ErrorModal } from '../../components/ErrorModal';
|
import { ErrorModal } from '../../components/ErrorModal';
|
||||||
import { GlobalModalContainer } from '../../components/GlobalModalContainer';
|
import { GlobalModalContainer } from '../../components/GlobalModalContainer';
|
||||||
|
@ -26,6 +23,7 @@ import { getIntl, getTheme } from '../selectors/user';
|
||||||
import { useGlobalModalActions } from '../ducks/globalModals';
|
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||||
import { SmartDeleteMessagesModal } from './DeleteMessagesModal';
|
import { SmartDeleteMessagesModal } from './DeleteMessagesModal';
|
||||||
import { SmartMessageRequestActionsConfirmation } from './MessageRequestActionsConfirmation';
|
import { SmartMessageRequestActionsConfirmation } from './MessageRequestActionsConfirmation';
|
||||||
|
import { getGlobalModalsState } from '../selectors/globalModals';
|
||||||
|
|
||||||
function renderEditHistoryMessagesModal(): JSX.Element {
|
function renderEditHistoryMessagesModal(): JSX.Element {
|
||||||
return <SmartEditHistoryMessagesModal />;
|
return <SmartEditHistoryMessagesModal />;
|
||||||
|
@ -71,7 +69,8 @@ function renderAboutContactModal(): JSX.Element {
|
||||||
return <SmartAboutContactModal />;
|
return <SmartAboutContactModal />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SmartGlobalModalContainer(): JSX.Element {
|
export const SmartGlobalModalContainer = memo(
|
||||||
|
function SmartGlobalModalContainer() {
|
||||||
const conversationsStoppingSend = useSelector(getConversationsStoppingSend);
|
const conversationsStoppingSend = useSelector(getConversationsStoppingSend);
|
||||||
const i18n = useSelector(getIntl);
|
const i18n = useSelector(getIntl);
|
||||||
const theme = useSelector(getTheme);
|
const theme = useSelector(getTheme);
|
||||||
|
@ -101,9 +100,7 @@ export function SmartGlobalModalContainer(): JSX.Element {
|
||||||
sendEditWarningData,
|
sendEditWarningData,
|
||||||
stickerPackPreviewId,
|
stickerPackPreviewId,
|
||||||
userNotFoundModalState,
|
userNotFoundModalState,
|
||||||
} = useSelector<StateType, GlobalModalsStateType>(
|
} = useSelector(getGlobalModalsState);
|
||||||
state => state.globalModals
|
|
||||||
);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
cancelAuthorizeArtCreator,
|
cancelAuthorizeArtCreator,
|
||||||
|
@ -126,7 +123,9 @@ export function SmartGlobalModalContainer(): JSX.Element {
|
||||||
|
|
||||||
const renderSafetyNumber = useCallback(
|
const renderSafetyNumber = useCallback(
|
||||||
() => (
|
() => (
|
||||||
<SmartSafetyNumberModal contactID={String(safetyNumberModalContactId)} />
|
<SmartSafetyNumberModal
|
||||||
|
contactID={String(safetyNumberModalContactId)}
|
||||||
|
/>
|
||||||
),
|
),
|
||||||
[safetyNumberModalContactId]
|
[safetyNumberModalContactId]
|
||||||
);
|
);
|
||||||
|
@ -162,7 +161,9 @@ export function SmartGlobalModalContainer(): JSX.Element {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GlobalModalContainer
|
<GlobalModalContainer
|
||||||
addUserToAnotherGroupModalContactId={addUserToAnotherGroupModalContactId}
|
addUserToAnotherGroupModalContactId={
|
||||||
|
addUserToAnotherGroupModalContactId
|
||||||
|
}
|
||||||
contactModalState={contactModalState}
|
contactModalState={contactModalState}
|
||||||
editHistoryMessages={editHistoryMessages}
|
editHistoryMessages={editHistoryMessages}
|
||||||
errorModalProps={errorModalProps}
|
errorModalProps={errorModalProps}
|
||||||
|
@ -215,4 +216,5 @@ export function SmartGlobalModalContainer(): JSX.Element {
|
||||||
confirmAuthorizeArtCreator={confirmAuthorizeArtCreator}
|
confirmAuthorizeArtCreator={confirmAuthorizeArtCreator}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
|
@ -1,34 +1,38 @@
|
||||||
// Copyright 2020 Signal Messenger, LLC
|
// Copyright 2020 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
import React, { memo } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import type { PropsDataType } from '../../components/conversation/conversation-details/GroupLinkManagement';
|
|
||||||
import type { StateType } from '../reducer';
|
|
||||||
import { GroupLinkManagement } from '../../components/conversation/conversation-details/GroupLinkManagement';
|
import { GroupLinkManagement } from '../../components/conversation/conversation-details/GroupLinkManagement';
|
||||||
import { getConversationSelector } from '../selectors/conversations';
|
import { getConversationSelector } from '../selectors/conversations';
|
||||||
import { getIntl } from '../selectors/user';
|
import { getIntl } from '../selectors/user';
|
||||||
import { mapDispatchToProps } from '../actions';
|
import { useConversationsActions } from '../ducks/conversations';
|
||||||
|
|
||||||
export type SmartGroupLinkManagementProps = {
|
export type SmartGroupLinkManagementProps = Readonly<{
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
};
|
}>;
|
||||||
|
|
||||||
const mapStateToProps = (
|
export const SmartGroupLinkManagement = memo(function SmartGroupLinkManagement({
|
||||||
state: StateType,
|
conversationId,
|
||||||
props: SmartGroupLinkManagementProps
|
}: SmartGroupLinkManagementProps) {
|
||||||
): PropsDataType => {
|
const i18n = useSelector(getIntl);
|
||||||
const conversation = getConversationSelector(state)(props.conversationId);
|
const conversationSelector = useSelector(getConversationSelector);
|
||||||
const isAdmin = Boolean(conversation?.areWeAdmin);
|
const conversation = conversationSelector(conversationId);
|
||||||
|
const isAdmin = conversation?.areWeAdmin ?? false;
|
||||||
return {
|
const {
|
||||||
...props,
|
changeHasGroupLink,
|
||||||
conversation,
|
generateNewGroupLink,
|
||||||
i18n: getIntl(state),
|
setAccessControlAddFromInviteLinkSetting,
|
||||||
isAdmin,
|
} = useConversationsActions();
|
||||||
};
|
return (
|
||||||
};
|
<GroupLinkManagement
|
||||||
|
i18n={i18n}
|
||||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
changeHasGroupLink={changeHasGroupLink}
|
||||||
|
conversation={conversation}
|
||||||
export const SmartGroupLinkManagement = smart(GroupLinkManagement);
|
generateNewGroupLink={generateNewGroupLink}
|
||||||
|
isAdmin={isAdmin}
|
||||||
|
setAccessControlAddFromInviteLinkSetting={
|
||||||
|
setAccessControlAddFromInviteLinkSetting
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
@ -1,19 +1,18 @@
|
||||||
// Copyright 2020 Signal Messenger, LLC
|
// Copyright 2020 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
import React, { memo, useCallback, useMemo } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { mapDispatchToProps } from '../actions';
|
|
||||||
import type { DataPropsType as GroupV1MigrationDialogPropsType } from '../../components/GroupV1MigrationDialog';
|
import type { DataPropsType as GroupV1MigrationDialogPropsType } from '../../components/GroupV1MigrationDialog';
|
||||||
import { GroupV1MigrationDialog } from '../../components/GroupV1MigrationDialog';
|
import { GroupV1MigrationDialog } from '../../components/GroupV1MigrationDialog';
|
||||||
import type { ConversationType } from '../ducks/conversations';
|
import { useConversationsActions } from '../ducks/conversations';
|
||||||
import type { StateType } from '../reducer';
|
|
||||||
import { getPreferredBadgeSelector } from '../selectors/badges';
|
import { getPreferredBadgeSelector } from '../selectors/badges';
|
||||||
import { getConversationSelector } from '../selectors/conversations';
|
import { getConversationSelector } from '../selectors/conversations';
|
||||||
|
|
||||||
import { getIntl, getTheme } from '../selectors/user';
|
import { getIntl, getTheme } from '../selectors/user';
|
||||||
import * as log from '../../logging/log';
|
import * as log from '../../logging/log';
|
||||||
|
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||||
|
|
||||||
export type PropsType = {
|
export type PropsType = {
|
||||||
|
readonly conversationId: string;
|
||||||
readonly droppedMemberIds: Array<string>;
|
readonly droppedMemberIds: Array<string>;
|
||||||
readonly invitedMemberIds: Array<string>;
|
readonly invitedMemberIds: Array<string>;
|
||||||
} & Omit<
|
} & Omit<
|
||||||
|
@ -21,37 +20,62 @@ export type PropsType = {
|
||||||
'i18n' | 'droppedMembers' | 'invitedMembers' | 'theme' | 'getPreferredBadge'
|
'i18n' | 'droppedMembers' | 'invitedMembers' | 'theme' | 'getPreferredBadge'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
const mapStateToProps = (
|
function isNonNullable<T>(value: T | null | undefined): value is T {
|
||||||
state: StateType,
|
return value != null;
|
||||||
props: PropsType
|
}
|
||||||
): GroupV1MigrationDialogPropsType => {
|
|
||||||
const getConversation = getConversationSelector(state);
|
|
||||||
const { droppedMemberIds, invitedMemberIds } = props;
|
|
||||||
|
|
||||||
const droppedMembers = droppedMemberIds
|
export const SmartGroupV1MigrationDialog = memo(
|
||||||
|
function SmartGroupV1MigrationDialog({
|
||||||
|
conversationId,
|
||||||
|
areWeInvited,
|
||||||
|
hasMigrated,
|
||||||
|
droppedMemberIds,
|
||||||
|
invitedMemberIds,
|
||||||
|
}: PropsType) {
|
||||||
|
const i18n = useSelector(getIntl);
|
||||||
|
const theme = useSelector(getTheme);
|
||||||
|
const getConversation = useSelector(getConversationSelector);
|
||||||
|
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||||
|
|
||||||
|
const { initiateMigrationToGroupV2 } = useConversationsActions();
|
||||||
|
const { closeGV2MigrationDialog } = useGlobalModalActions();
|
||||||
|
|
||||||
|
const droppedMembers = useMemo(() => {
|
||||||
|
const result = droppedMemberIds
|
||||||
.map(getConversation)
|
.map(getConversation)
|
||||||
.filter(Boolean) as Array<ConversationType>;
|
.filter(isNonNullable);
|
||||||
if (droppedMembers.length !== droppedMemberIds.length) {
|
if (result.length !== droppedMemberIds.length) {
|
||||||
log.warn('smart/GroupV1MigrationDialog: droppedMembers length changed');
|
log.warn('smart/GroupV1MigrationDialog: droppedMembers length changed');
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
|
}, [droppedMemberIds, getConversation]);
|
||||||
|
|
||||||
const invitedMembers = invitedMemberIds
|
const invitedMembers = useMemo(() => {
|
||||||
|
const result = invitedMemberIds
|
||||||
.map(getConversation)
|
.map(getConversation)
|
||||||
.filter(Boolean) as Array<ConversationType>;
|
.filter(isNonNullable);
|
||||||
if (invitedMembers.length !== invitedMemberIds.length) {
|
if (result.length !== invitedMemberIds.length) {
|
||||||
log.warn('smart/GroupV1MigrationDialog: invitedMembers length changed');
|
log.warn('smart/GroupV1MigrationDialog: invitedMembers length changed');
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
|
}, [invitedMemberIds, getConversation]);
|
||||||
|
|
||||||
return {
|
const handleMigrate = useCallback(() => {
|
||||||
...props,
|
initiateMigrationToGroupV2(conversationId);
|
||||||
droppedMembers,
|
}, [initiateMigrationToGroupV2, conversationId]);
|
||||||
getPreferredBadge: getPreferredBadgeSelector(state),
|
|
||||||
invitedMembers,
|
|
||||||
i18n: getIntl(state),
|
|
||||||
theme: getTheme(state),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
return (
|
||||||
|
<GroupV1MigrationDialog
|
||||||
export const SmartGroupV1MigrationDialog = smart(GroupV1MigrationDialog);
|
i18n={i18n}
|
||||||
|
theme={theme}
|
||||||
|
areWeInvited={areWeInvited}
|
||||||
|
hasMigrated={hasMigrated}
|
||||||
|
getPreferredBadge={getPreferredBadge}
|
||||||
|
droppedMembers={droppedMembers}
|
||||||
|
invitedMembers={invitedMembers}
|
||||||
|
onMigrate={handleMigrate}
|
||||||
|
onClose={closeGV2MigrationDialog}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
|
@ -1,34 +1,38 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
import React, { memo } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { mapDispatchToProps } from '../actions';
|
|
||||||
import type { PropsType as GroupV2JoinDialogPropsType } from '../../components/GroupV2JoinDialog';
|
import type { PropsType as GroupV2JoinDialogPropsType } from '../../components/GroupV2JoinDialog';
|
||||||
import { GroupV2JoinDialog } from '../../components/GroupV2JoinDialog';
|
import { GroupV2JoinDialog } from '../../components/GroupV2JoinDialog';
|
||||||
import type { StateType } from '../reducer';
|
|
||||||
|
|
||||||
import { getIntl } from '../selectors/user';
|
import { getIntl } from '../selectors/user';
|
||||||
import { getPreJoinConversation } from '../selectors/conversations';
|
import { getPreJoinConversation } from '../selectors/conversations';
|
||||||
|
|
||||||
export type PropsType = Pick<GroupV2JoinDialogPropsType, 'join' | 'onClose'>;
|
export type SmartGroupV2JoinDialogProps = Pick<
|
||||||
|
GroupV2JoinDialogPropsType,
|
||||||
|
'join' | 'onClose'
|
||||||
|
>;
|
||||||
|
|
||||||
const mapStateToProps = (
|
export const SmartGroupV2JoinDialog = memo(function SmartGroupV2JoinDialog({
|
||||||
state: StateType,
|
join,
|
||||||
props: PropsType
|
onClose,
|
||||||
): GroupV2JoinDialogPropsType => {
|
}: SmartGroupV2JoinDialogProps) {
|
||||||
const preJoinConversation = getPreJoinConversation(state);
|
const i18n = useSelector(getIntl);
|
||||||
|
const preJoinConversation = useSelector(getPreJoinConversation);
|
||||||
if (!preJoinConversation) {
|
if (preJoinConversation == null) {
|
||||||
throw new Error('smart/GroupV2JoinDialog: No pre-join conversation!');
|
throw new Error('smart/GroupV2JoinDialog: No pre-join conversation!');
|
||||||
}
|
}
|
||||||
|
const { memberCount, title, groupDescription, approvalRequired, avatar } =
|
||||||
return {
|
preJoinConversation;
|
||||||
...props,
|
return (
|
||||||
...preJoinConversation,
|
<GroupV2JoinDialog
|
||||||
i18n: getIntl(state),
|
approvalRequired={approvalRequired}
|
||||||
};
|
avatar={avatar}
|
||||||
};
|
groupDescription={groupDescription}
|
||||||
|
i18n={i18n}
|
||||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
join={join}
|
||||||
|
memberCount={memberCount}
|
||||||
export const SmartGroupV2JoinDialog = smart(GroupV2JoinDialog);
|
onClose={onClose}
|
||||||
|
title={title}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
@ -1,32 +1,35 @@
|
||||||
// Copyright 2020 Signal Messenger, LLC
|
// Copyright 2020 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { connect } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
import React, { memo } from 'react';
|
||||||
import type { StateType } from '../reducer';
|
|
||||||
import type { PropsDataType } from '../../components/conversation/conversation-details/GroupV2Permissions';
|
|
||||||
import { mapDispatchToProps } from '../actions';
|
|
||||||
import { GroupV2Permissions } from '../../components/conversation/conversation-details/GroupV2Permissions';
|
import { GroupV2Permissions } from '../../components/conversation/conversation-details/GroupV2Permissions';
|
||||||
import { getConversationSelector } from '../selectors/conversations';
|
import { getConversationSelector } from '../selectors/conversations';
|
||||||
import { getIntl } from '../selectors/user';
|
import { getIntl } from '../selectors/user';
|
||||||
|
import { useConversationsActions } from '../ducks/conversations';
|
||||||
|
|
||||||
export type SmartGroupV2PermissionsProps = {
|
export type SmartGroupV2PermissionsProps = {
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (
|
export const SmartGroupV2Permissions = memo(function SmartGroupV2Permissions({
|
||||||
state: StateType,
|
conversationId,
|
||||||
props: SmartGroupV2PermissionsProps
|
}: SmartGroupV2PermissionsProps) {
|
||||||
): PropsDataType => {
|
const i18n = useSelector(getIntl);
|
||||||
const conversation = getConversationSelector(state)(props.conversationId);
|
const conversationSelector = useSelector(getConversationSelector);
|
||||||
|
const conversation = conversationSelector(conversationId);
|
||||||
return {
|
const {
|
||||||
...props,
|
setAccessControlAttributesSetting,
|
||||||
conversation,
|
setAccessControlMembersSetting,
|
||||||
i18n: getIntl(state),
|
setAnnouncementsOnly,
|
||||||
};
|
} = useConversationsActions();
|
||||||
};
|
return (
|
||||||
|
<GroupV2Permissions
|
||||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
i18n={i18n}
|
||||||
|
conversation={conversation}
|
||||||
export const SmartGroupV2Permissions = smart(GroupV2Permissions);
|
setAccessControlAttributesSetting={setAccessControlAttributesSetting}
|
||||||
|
setAccessControlMembersSetting={setAccessControlMembersSetting}
|
||||||
|
setAnnouncementsOnly={setAnnouncementsOnly}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
@ -1,41 +1,77 @@
|
||||||
// Copyright 2020 Signal Messenger, LLC
|
// Copyright 2020 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
import React, { memo } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { mapDispatchToProps } from '../actions';
|
|
||||||
|
|
||||||
import { ConversationHero } from '../../components/conversation/ConversationHero';
|
import { ConversationHero } from '../../components/conversation/ConversationHero';
|
||||||
|
|
||||||
import type { StateType } from '../reducer';
|
|
||||||
import { getPreferredBadgeSelector } from '../selectors/badges';
|
import { getPreferredBadgeSelector } from '../selectors/badges';
|
||||||
import { getIntl, getTheme } from '../selectors/user';
|
import { getIntl, getTheme } from '../selectors/user';
|
||||||
import { getHasStoriesSelector } from '../selectors/stories2';
|
import { getHasStoriesSelector } from '../selectors/stories2';
|
||||||
import { isSignalConversation } from '../../util/isSignalConversation';
|
import { isSignalConversation } from '../../util/isSignalConversation';
|
||||||
|
import { getConversationSelector } from '../selectors/conversations';
|
||||||
|
import { useConversationsActions } from '../ducks/conversations';
|
||||||
|
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||||
|
import { useStoriesActions } from '../ducks/stories';
|
||||||
|
|
||||||
type ExternalProps = {
|
type SmartHeroRowProps = Readonly<{
|
||||||
id: string;
|
id: string;
|
||||||
};
|
}>;
|
||||||
|
|
||||||
const mapStateToProps = (state: StateType, props: ExternalProps) => {
|
export const SmartHeroRow = memo(function SmartHeroRow({
|
||||||
const { id } = props;
|
id,
|
||||||
|
}: SmartHeroRowProps) {
|
||||||
const conversation = state.conversations.conversationLookup[id];
|
const i18n = useSelector(getIntl);
|
||||||
|
const theme = useSelector(getTheme);
|
||||||
if (!conversation) {
|
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||||
|
const hasStoriesSelector = useSelector(getHasStoriesSelector);
|
||||||
|
const conversationSelector = useSelector(getConversationSelector);
|
||||||
|
const conversation = conversationSelector(id);
|
||||||
|
if (conversation == null) {
|
||||||
throw new Error(`Did not find conversation ${id} in state!`);
|
throw new Error(`Did not find conversation ${id} in state!`);
|
||||||
}
|
}
|
||||||
|
const badge = getPreferredBadge(conversation.badges);
|
||||||
return {
|
const hasStories = hasStoriesSelector(id);
|
||||||
i18n: getIntl(state),
|
const isSignalConversationValue = isSignalConversation(conversation);
|
||||||
...conversation,
|
const { unblurAvatar, updateSharedGroups } = useConversationsActions();
|
||||||
conversationType: conversation.type,
|
const { toggleAboutContactModal } = useGlobalModalActions();
|
||||||
hasStories: getHasStoriesSelector(state)(id),
|
const { viewUserStories } = useStoriesActions();
|
||||||
badge: getPreferredBadgeSelector(state)(conversation.badges),
|
const {
|
||||||
isSignalConversation: isSignalConversation(conversation),
|
about,
|
||||||
theme: getTheme(state),
|
acceptedMessageRequest,
|
||||||
};
|
avatarPath,
|
||||||
};
|
groupDescription,
|
||||||
|
isMe,
|
||||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
membersCount,
|
||||||
|
phoneNumber,
|
||||||
export const SmartHeroRow = smart(ConversationHero);
|
profileName,
|
||||||
|
sharedGroupNames,
|
||||||
|
title,
|
||||||
|
type,
|
||||||
|
unblurredAvatarPath,
|
||||||
|
} = conversation;
|
||||||
|
return (
|
||||||
|
<ConversationHero
|
||||||
|
about={about}
|
||||||
|
acceptedMessageRequest={acceptedMessageRequest}
|
||||||
|
avatarPath={avatarPath}
|
||||||
|
badge={badge}
|
||||||
|
conversationType={type}
|
||||||
|
groupDescription={groupDescription}
|
||||||
|
hasStories={hasStories}
|
||||||
|
i18n={i18n}
|
||||||
|
id={id}
|
||||||
|
isMe={isMe}
|
||||||
|
isSignalConversation={isSignalConversationValue}
|
||||||
|
membersCount={membersCount}
|
||||||
|
phoneNumber={phoneNumber}
|
||||||
|
profileName={profileName}
|
||||||
|
sharedGroupNames={sharedGroupNames}
|
||||||
|
theme={theme}
|
||||||
|
title={title}
|
||||||
|
toggleAboutContactModal={toggleAboutContactModal}
|
||||||
|
unblurAvatar={unblurAvatar}
|
||||||
|
unblurredAvatarPath={unblurredAvatarPath}
|
||||||
|
updateSharedGroups={updateSharedGroups}
|
||||||
|
viewUserStories={viewUserStories}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
// Copyright 2022 Signal Messenger, LLC
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React from 'react';
|
import React, { memo } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import type { AppStateType } from '../ducks/app';
|
|
||||||
import type { StateType } from '../reducer';
|
import type { StateType } from '../reducer';
|
||||||
import { Inbox } from '../../components/Inbox';
|
import { Inbox } from '../../components/Inbox';
|
||||||
import { getIntl } from '../selectors/user';
|
import { getIntl } from '../selectors/user';
|
||||||
|
@ -37,19 +36,19 @@ function renderStoriesTab() {
|
||||||
return <SmartStoriesTab />;
|
return <SmartStoriesTab />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SmartInbox(): JSX.Element {
|
export const SmartInbox = memo(function SmartInbox(): JSX.Element {
|
||||||
const i18n = useSelector(getIntl);
|
const i18n = useSelector(getIntl);
|
||||||
const isCustomizingPreferredReactions = useSelector(
|
const isCustomizingPreferredReactions = useSelector(
|
||||||
getIsCustomizingPreferredReactions
|
getIsCustomizingPreferredReactions
|
||||||
);
|
);
|
||||||
const envelopeTimestamp = useSelector<StateType, number | undefined>(
|
const envelopeTimestamp = useSelector(
|
||||||
state => state.inbox.envelopeTimestamp
|
(state: StateType) => state.inbox.envelopeTimestamp
|
||||||
);
|
);
|
||||||
const firstEnvelopeTimestamp = useSelector<StateType, number | undefined>(
|
const firstEnvelopeTimestamp = useSelector(
|
||||||
state => state.inbox.firstEnvelopeTimestamp
|
(state: StateType) => state.inbox.firstEnvelopeTimestamp
|
||||||
);
|
);
|
||||||
const { hasInitialLoadCompleted } = useSelector<StateType, AppStateType>(
|
const { hasInitialLoadCompleted } = useSelector(
|
||||||
state => state.app
|
(state: StateType) => state.app
|
||||||
);
|
);
|
||||||
|
|
||||||
const navTabsCollapsed = useSelector(getNavTabsCollapsed);
|
const navTabsCollapsed = useSelector(getNavTabsCollapsed);
|
||||||
|
@ -73,4 +72,4 @@ export function SmartInbox(): JSX.Element {
|
||||||
renderStoriesTab={renderStoriesTab}
|
renderStoriesTab={renderStoriesTab}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
|
@ -1,16 +1,14 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import type { ComponentProps, ReactElement } from 'react';
|
import type { ComponentProps } from 'react';
|
||||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
import React, { memo, useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import pTimeout, { TimeoutError } from 'p-timeout';
|
import pTimeout, { TimeoutError } from 'p-timeout';
|
||||||
|
|
||||||
import { getIntl } from '../selectors/user';
|
import { getIntl } from '../selectors/user';
|
||||||
import { getUpdatesState } from '../selectors/updates';
|
import { getUpdatesState } from '../selectors/updates';
|
||||||
import { useUpdatesActions } from '../ducks/updates';
|
import { useUpdatesActions } from '../ducks/updates';
|
||||||
import { hasExpired as hasExpiredSelector } from '../selectors/expiration';
|
import { hasExpired as hasExpiredSelector } from '../selectors/expiration';
|
||||||
|
|
||||||
import * as log from '../../logging/log';
|
import * as log from '../../logging/log';
|
||||||
import type { Loadable } from '../../util/loadable';
|
import type { Loadable } from '../../util/loadable';
|
||||||
import { LoadingState } from '../../util/loadable';
|
import { LoadingState } from '../../util/loadable';
|
||||||
|
@ -87,7 +85,7 @@ function getInstallError(err: unknown): InstallError {
|
||||||
return InstallError.UnknownError;
|
return InstallError.UnknownError;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SmartInstallScreen(): ReactElement {
|
export const SmartInstallScreen = memo(function SmartInstallScreen() {
|
||||||
const i18n = useSelector(getIntl);
|
const i18n = useSelector(getIntl);
|
||||||
const updates = useSelector(getUpdatesState);
|
const updates = useSelector(getUpdatesState);
|
||||||
const { startUpdate } = useUpdatesActions();
|
const { startUpdate } = useUpdatesActions();
|
||||||
|
@ -339,4 +337,4 @@ export function SmartInstallScreen(): ReactElement {
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
|
@ -1,23 +1,71 @@
|
||||||
// Copyright 2019 Signal Messenger, LLC
|
// Copyright 2019 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
import { mapDispatchToProps } from '../actions';
|
import React, { memo } from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import type { PropsType as DialogExpiredBuildPropsType } from '../../components/DialogExpiredBuild';
|
||||||
|
import { DialogExpiredBuild } from '../../components/DialogExpiredBuild';
|
||||||
import type { PropsType as LeftPanePropsType } from '../../components/LeftPane';
|
import type { PropsType as LeftPanePropsType } from '../../components/LeftPane';
|
||||||
import { LeftPane } from '../../components/LeftPane';
|
import { LeftPane } from '../../components/LeftPane';
|
||||||
import { DialogExpiredBuild } from '../../components/DialogExpiredBuild';
|
import type { NavTabPanelProps } from '../../components/NavTabs';
|
||||||
import type { PropsType as DialogExpiredBuildPropsType } from '../../components/DialogExpiredBuild';
|
import type { WidthBreakpoint } from '../../components/_util';
|
||||||
import type { StateType } from '../reducer';
|
import {
|
||||||
import { missingCaseError } from '../../util/missingCaseError';
|
getGroupSizeHardLimit,
|
||||||
import { lookupConversationWithoutServiceId } from '../../util/lookupConversationWithoutServiceId';
|
getGroupSizeRecommendedLimit,
|
||||||
import { isDone as isRegistrationDone } from '../../util/registration';
|
} from '../../groups/limits';
|
||||||
import { getCountryDataForLocale } from '../../util/getCountryData';
|
|
||||||
import { getUsernameFromSearch } from '../../util/Username';
|
|
||||||
import { LeftPaneMode } from '../../types/leftPane';
|
import { LeftPaneMode } from '../../types/leftPane';
|
||||||
|
import { getUsernameFromSearch } from '../../util/Username';
|
||||||
|
import { getCountryDataForLocale } from '../../util/getCountryData';
|
||||||
|
import { lookupConversationWithoutServiceId } from '../../util/lookupConversationWithoutServiceId';
|
||||||
|
import { missingCaseError } from '../../util/missingCaseError';
|
||||||
|
import { isDone as isRegistrationDone } from '../../util/registration';
|
||||||
|
import { useCallingActions } from '../ducks/calling';
|
||||||
|
import { useConversationsActions } from '../ducks/conversations';
|
||||||
import { ComposerStep, OneTimeModalState } from '../ducks/conversationsEnums';
|
import { ComposerStep, OneTimeModalState } from '../ducks/conversationsEnums';
|
||||||
|
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||||
|
import { useItemsActions } from '../ducks/items';
|
||||||
|
import { useNetworkActions } from '../ducks/network';
|
||||||
|
import { useSearchActions } from '../ducks/search';
|
||||||
|
import { useUsernameActions } from '../ducks/username';
|
||||||
|
import type { StateType } from '../reducer';
|
||||||
|
import { getPreferredBadgeSelector } from '../selectors/badges';
|
||||||
|
import {
|
||||||
|
getComposeAvatarData,
|
||||||
|
getComposeGroupAvatar,
|
||||||
|
getComposeGroupExpireTimer,
|
||||||
|
getComposeGroupName,
|
||||||
|
getComposeSelectedContacts,
|
||||||
|
getComposerConversationSearchTerm,
|
||||||
|
getComposerSelectedRegion,
|
||||||
|
getComposerStep,
|
||||||
|
getComposerUUIDFetchState,
|
||||||
|
getFilteredCandidateContactsForNewGroup,
|
||||||
|
getFilteredComposeContacts,
|
||||||
|
getFilteredComposeGroups,
|
||||||
|
getLeftPaneLists,
|
||||||
|
getMaximumGroupSizeModalState,
|
||||||
|
getMe,
|
||||||
|
getRecommendedGroupSizeModalState,
|
||||||
|
getSelectedConversationId,
|
||||||
|
getShowArchived,
|
||||||
|
getTargetedMessage,
|
||||||
|
hasGroupCreationError,
|
||||||
|
isCreatingGroup,
|
||||||
|
isEditingAvatar,
|
||||||
|
} from '../selectors/conversations';
|
||||||
|
import { getCrashReportCount } from '../selectors/crashReports';
|
||||||
|
import { hasExpired } from '../selectors/expiration';
|
||||||
|
import {
|
||||||
|
getNavTabsCollapsed,
|
||||||
|
getPreferredLeftPaneWidth,
|
||||||
|
getUsernameCorrupted,
|
||||||
|
getUsernameLinkCorrupted,
|
||||||
|
} from '../selectors/items';
|
||||||
|
import {
|
||||||
|
getChallengeStatus,
|
||||||
|
hasNetworkDialog as getHasNetworkDialog,
|
||||||
|
} from '../selectors/network';
|
||||||
import {
|
import {
|
||||||
getIsSearching,
|
getIsSearching,
|
||||||
getQuery,
|
getQuery,
|
||||||
|
@ -26,65 +74,26 @@ import {
|
||||||
getStartSearchCounter,
|
getStartSearchCounter,
|
||||||
isSearching,
|
isSearching,
|
||||||
} from '../selectors/search';
|
} from '../selectors/search';
|
||||||
|
import {
|
||||||
|
isUpdateDownloaded as getIsUpdateDownloaded,
|
||||||
|
isOSUnsupported,
|
||||||
|
isUpdateDialogVisible,
|
||||||
|
} from '../selectors/updates';
|
||||||
import {
|
import {
|
||||||
getIntl,
|
getIntl,
|
||||||
|
getIsMacOS,
|
||||||
getRegionCode,
|
getRegionCode,
|
||||||
getTheme,
|
getTheme,
|
||||||
getIsMacOS,
|
|
||||||
} from '../selectors/user';
|
} from '../selectors/user';
|
||||||
import { hasExpired } from '../selectors/expiration';
|
import { SmartCaptchaDialog } from './CaptchaDialog';
|
||||||
import {
|
import { SmartCrashReportDialog } from './CrashReportDialog';
|
||||||
isUpdateDialogVisible,
|
|
||||||
isUpdateDownloaded,
|
|
||||||
isOSUnsupported,
|
|
||||||
} from '../selectors/updates';
|
|
||||||
import { getPreferredBadgeSelector } from '../selectors/badges';
|
|
||||||
import { hasNetworkDialog } from '../selectors/network';
|
|
||||||
import {
|
|
||||||
getPreferredLeftPaneWidth,
|
|
||||||
getUsernameCorrupted,
|
|
||||||
getUsernameLinkCorrupted,
|
|
||||||
getNavTabsCollapsed,
|
|
||||||
} from '../selectors/items';
|
|
||||||
import {
|
|
||||||
getComposeAvatarData,
|
|
||||||
getComposeGroupAvatar,
|
|
||||||
getComposeGroupExpireTimer,
|
|
||||||
getComposeGroupName,
|
|
||||||
getComposerConversationSearchTerm,
|
|
||||||
getComposerSelectedRegion,
|
|
||||||
getComposerStep,
|
|
||||||
getComposerUUIDFetchState,
|
|
||||||
getComposeSelectedContacts,
|
|
||||||
getFilteredCandidateContactsForNewGroup,
|
|
||||||
getFilteredComposeContacts,
|
|
||||||
getFilteredComposeGroups,
|
|
||||||
getLeftPaneLists,
|
|
||||||
getMaximumGroupSizeModalState,
|
|
||||||
getMe,
|
|
||||||
getRecommendedGroupSizeModalState,
|
|
||||||
getSelectedConversationId,
|
|
||||||
getTargetedMessage,
|
|
||||||
getShowArchived,
|
|
||||||
hasGroupCreationError,
|
|
||||||
isCreatingGroup,
|
|
||||||
isEditingAvatar,
|
|
||||||
} from '../selectors/conversations';
|
|
||||||
import type { WidthBreakpoint } from '../../components/_util';
|
|
||||||
import {
|
|
||||||
getGroupSizeRecommendedLimit,
|
|
||||||
getGroupSizeHardLimit,
|
|
||||||
} from '../../groups/limits';
|
|
||||||
|
|
||||||
import { SmartMessageSearchResult } from './MessageSearchResult';
|
import { SmartMessageSearchResult } from './MessageSearchResult';
|
||||||
import { SmartNetworkStatus } from './NetworkStatus';
|
import { SmartNetworkStatus } from './NetworkStatus';
|
||||||
import { SmartRelinkDialog } from './RelinkDialog';
|
import { SmartRelinkDialog } from './RelinkDialog';
|
||||||
import { SmartUnsupportedOSDialog } from './UnsupportedOSDialog';
|
|
||||||
import { SmartToastManager } from './ToastManager';
|
import { SmartToastManager } from './ToastManager';
|
||||||
import type { PropsType as SmartUnsupportedOSDialogPropsType } from './UnsupportedOSDialog';
|
import type { PropsType as SmartUnsupportedOSDialogPropsType } from './UnsupportedOSDialog';
|
||||||
|
import { SmartUnsupportedOSDialog } from './UnsupportedOSDialog';
|
||||||
import { SmartUpdateDialog } from './UpdateDialog';
|
import { SmartUpdateDialog } from './UpdateDialog';
|
||||||
import { SmartCaptchaDialog } from './CaptchaDialog';
|
|
||||||
import { SmartCrashReportDialog } from './CrashReportDialog';
|
|
||||||
|
|
||||||
function renderMessageSearchResult(id: string): JSX.Element {
|
function renderMessageSearchResult(id: string): JSX.Element {
|
||||||
return <SmartMessageSearchResult id={id} />;
|
return <SmartMessageSearchResult id={id} />;
|
||||||
|
@ -120,7 +129,7 @@ function renderUnsupportedOSDialog(
|
||||||
): JSX.Element {
|
): JSX.Element {
|
||||||
return <SmartUnsupportedOSDialog {...props} />;
|
return <SmartUnsupportedOSDialog {...props} />;
|
||||||
}
|
}
|
||||||
function renderToastManager(props: {
|
function renderToastManagerWithMegaphone(props: {
|
||||||
containerWidthBreakpoint: WidthBreakpoint;
|
containerWidthBreakpoint: WidthBreakpoint;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
return <SmartToastManager {...props} />;
|
return <SmartToastManager {...props} />;
|
||||||
|
@ -243,15 +252,81 @@ const getModeSpecificProps = (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state: StateType) => {
|
export const SmartLeftPane = memo(function SmartLeftPane({
|
||||||
const hasUpdateDialog = isUpdateDialogVisible(state);
|
hasFailedStorySends,
|
||||||
const hasUnsupportedOS = isOSUnsupported(state);
|
hasPendingUpdate,
|
||||||
const usernameCorrupted = getUsernameCorrupted(state);
|
otherTabsUnreadStats,
|
||||||
const usernameLinkCorrupted = getUsernameLinkCorrupted(state);
|
}: NavTabPanelProps) {
|
||||||
|
const challengeStatus = useSelector(getChallengeStatus);
|
||||||
|
const composerStep = useSelector(getComposerStep);
|
||||||
|
const crashReportCount = useSelector(getCrashReportCount);
|
||||||
|
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||||
|
const hasAppExpired = useSelector(hasExpired);
|
||||||
|
const hasNetworkDialog = useSelector(getHasNetworkDialog);
|
||||||
|
const hasSearchQuery = useSelector(isSearching);
|
||||||
|
const hasUnsupportedOS = useSelector(isOSUnsupported);
|
||||||
|
const hasUpdateDialog = useSelector(isUpdateDialogVisible);
|
||||||
|
const i18n = useSelector(getIntl);
|
||||||
|
const isMacOS = useSelector(getIsMacOS);
|
||||||
|
const isUpdateDownloaded = useSelector(getIsUpdateDownloaded);
|
||||||
|
const modeSpecificProps = useSelector(getModeSpecificProps);
|
||||||
|
const navTabsCollapsed = useSelector(getNavTabsCollapsed);
|
||||||
|
const preferredWidthFromStorage = useSelector(getPreferredLeftPaneWidth);
|
||||||
|
const selectedConversationId = useSelector(getSelectedConversationId);
|
||||||
|
const showArchived = useSelector(getShowArchived);
|
||||||
|
const targetedMessage = useSelector(getTargetedMessage);
|
||||||
|
const theme = useSelector(getTheme);
|
||||||
|
const usernameCorrupted = useSelector(getUsernameCorrupted);
|
||||||
|
const usernameLinkCorrupted = useSelector(getUsernameLinkCorrupted);
|
||||||
|
|
||||||
|
const {
|
||||||
|
blockConversation,
|
||||||
|
clearGroupCreationError,
|
||||||
|
closeMaximumGroupSizeModal,
|
||||||
|
closeRecommendedGroupSizeModal,
|
||||||
|
composeDeleteAvatarFromDisk,
|
||||||
|
composeReplaceAvatar,
|
||||||
|
composeSaveAvatarToDisk,
|
||||||
|
createGroup,
|
||||||
|
removeConversation,
|
||||||
|
setComposeGroupAvatar,
|
||||||
|
setComposeGroupExpireTimer,
|
||||||
|
setComposeGroupName,
|
||||||
|
setComposeSearchTerm,
|
||||||
|
setComposeSelectedRegion,
|
||||||
|
setIsFetchingUUID,
|
||||||
|
showArchivedConversations,
|
||||||
|
showChooseGroupMembers,
|
||||||
|
showConversation,
|
||||||
|
showFindByPhoneNumber,
|
||||||
|
showFindByUsername,
|
||||||
|
showInbox,
|
||||||
|
startComposing,
|
||||||
|
startSettingGroupMetadata,
|
||||||
|
toggleComposeEditingAvatar,
|
||||||
|
toggleConversationInChooseMembers,
|
||||||
|
} = useConversationsActions();
|
||||||
|
const {
|
||||||
|
clearConversationSearch,
|
||||||
|
clearSearch,
|
||||||
|
searchInConversation,
|
||||||
|
startSearch,
|
||||||
|
updateSearchTerm,
|
||||||
|
} = useSearchActions();
|
||||||
|
const {
|
||||||
|
onOutgoingAudioCallInConversation,
|
||||||
|
onOutgoingVideoCallInConversation,
|
||||||
|
} = useCallingActions();
|
||||||
|
const { openUsernameReservationModal } = useUsernameActions();
|
||||||
|
const { savePreferredLeftPaneWidth, toggleNavTabsCollapse } =
|
||||||
|
useItemsActions();
|
||||||
|
const { setChallengeStatus } = useNetworkActions();
|
||||||
|
const { showUserNotFoundModal, toggleProfileEditor } =
|
||||||
|
useGlobalModalActions();
|
||||||
|
|
||||||
let hasExpiredDialog = false;
|
let hasExpiredDialog = false;
|
||||||
let unsupportedOSDialogType: 'error' | 'warning' | undefined;
|
let unsupportedOSDialogType: 'error' | 'warning' | undefined;
|
||||||
if (hasExpired(state)) {
|
if (hasAppExpired) {
|
||||||
if (hasUnsupportedOS) {
|
if (hasUnsupportedOS) {
|
||||||
unsupportedOSDialogType = 'error';
|
unsupportedOSDialogType = 'error';
|
||||||
} else {
|
} else {
|
||||||
|
@ -261,49 +336,87 @@ const mapStateToProps = (state: StateType) => {
|
||||||
unsupportedOSDialogType = 'warning';
|
unsupportedOSDialogType = 'warning';
|
||||||
}
|
}
|
||||||
|
|
||||||
const composerStep = getComposerStep(state);
|
const hasRelinkDialog = !isRegistrationDone();
|
||||||
const showArchived = getShowArchived(state);
|
|
||||||
const hasSearchQuery = isSearching(state);
|
|
||||||
|
|
||||||
return {
|
const renderToastManager =
|
||||||
hasNetworkDialog: hasNetworkDialog(state),
|
|
||||||
hasExpiredDialog,
|
|
||||||
hasRelinkDialog: !isRegistrationDone(),
|
|
||||||
hasUpdateDialog,
|
|
||||||
isUpdateDownloaded: isUpdateDownloaded(state),
|
|
||||||
unsupportedOSDialogType,
|
|
||||||
usernameCorrupted,
|
|
||||||
usernameLinkCorrupted,
|
|
||||||
|
|
||||||
modeSpecificProps: getModeSpecificProps(state),
|
|
||||||
navTabsCollapsed: getNavTabsCollapsed(state),
|
|
||||||
preferredWidthFromStorage: getPreferredLeftPaneWidth(state),
|
|
||||||
selectedConversationId: getSelectedConversationId(state),
|
|
||||||
targetedMessageId: getTargetedMessage(state)?.id,
|
|
||||||
showArchived,
|
|
||||||
getPreferredBadge: getPreferredBadgeSelector(state),
|
|
||||||
i18n: getIntl(state),
|
|
||||||
isMacOS: getIsMacOS(state),
|
|
||||||
regionCode: getRegionCode(state),
|
|
||||||
challengeStatus: state.network.challengeStatus,
|
|
||||||
crashReportCount: state.crashReports.count,
|
|
||||||
renderMessageSearchResult,
|
|
||||||
renderNetworkStatus,
|
|
||||||
renderRelinkDialog,
|
|
||||||
renderUpdateDialog,
|
|
||||||
renderCaptchaDialog,
|
|
||||||
renderCrashReportDialog,
|
|
||||||
renderExpiredBuildDialog,
|
|
||||||
renderUnsupportedOSDialog,
|
|
||||||
renderToastManager:
|
|
||||||
composerStep == null && !showArchived && !hasSearchQuery
|
composerStep == null && !showArchived && !hasSearchQuery
|
||||||
? renderToastManager
|
? renderToastManagerWithMegaphone
|
||||||
: renderToastManagerWithoutMegaphone,
|
: renderToastManagerWithoutMegaphone;
|
||||||
lookupConversationWithoutServiceId,
|
|
||||||
theme: getTheme(state),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
const targetedMessageId = targetedMessage?.id;
|
||||||
|
|
||||||
export const SmartLeftPane = smart(LeftPane);
|
return (
|
||||||
|
<LeftPane
|
||||||
|
blockConversation={blockConversation}
|
||||||
|
challengeStatus={challengeStatus}
|
||||||
|
clearConversationSearch={clearConversationSearch}
|
||||||
|
clearGroupCreationError={clearGroupCreationError}
|
||||||
|
clearSearch={clearSearch}
|
||||||
|
closeMaximumGroupSizeModal={closeMaximumGroupSizeModal}
|
||||||
|
closeRecommendedGroupSizeModal={closeRecommendedGroupSizeModal}
|
||||||
|
composeDeleteAvatarFromDisk={composeDeleteAvatarFromDisk}
|
||||||
|
composeReplaceAvatar={composeReplaceAvatar}
|
||||||
|
composeSaveAvatarToDisk={composeSaveAvatarToDisk}
|
||||||
|
crashReportCount={crashReportCount}
|
||||||
|
createGroup={createGroup}
|
||||||
|
getPreferredBadge={getPreferredBadge}
|
||||||
|
hasExpiredDialog={hasExpiredDialog}
|
||||||
|
hasFailedStorySends={hasFailedStorySends}
|
||||||
|
hasNetworkDialog={hasNetworkDialog}
|
||||||
|
hasPendingUpdate={hasPendingUpdate}
|
||||||
|
hasRelinkDialog={hasRelinkDialog}
|
||||||
|
hasUpdateDialog={hasUpdateDialog}
|
||||||
|
i18n={i18n}
|
||||||
|
isMacOS={isMacOS}
|
||||||
|
isUpdateDownloaded={isUpdateDownloaded}
|
||||||
|
lookupConversationWithoutServiceId={lookupConversationWithoutServiceId}
|
||||||
|
modeSpecificProps={modeSpecificProps}
|
||||||
|
navTabsCollapsed={navTabsCollapsed}
|
||||||
|
onOutgoingAudioCallInConversation={onOutgoingAudioCallInConversation}
|
||||||
|
onOutgoingVideoCallInConversation={onOutgoingVideoCallInConversation}
|
||||||
|
openUsernameReservationModal={openUsernameReservationModal}
|
||||||
|
otherTabsUnreadStats={otherTabsUnreadStats}
|
||||||
|
preferredWidthFromStorage={preferredWidthFromStorage}
|
||||||
|
removeConversation={removeConversation}
|
||||||
|
renderCaptchaDialog={renderCaptchaDialog}
|
||||||
|
renderCrashReportDialog={renderCrashReportDialog}
|
||||||
|
renderExpiredBuildDialog={renderExpiredBuildDialog}
|
||||||
|
renderMessageSearchResult={renderMessageSearchResult}
|
||||||
|
renderNetworkStatus={renderNetworkStatus}
|
||||||
|
renderRelinkDialog={renderRelinkDialog}
|
||||||
|
renderToastManager={renderToastManager}
|
||||||
|
renderUnsupportedOSDialog={renderUnsupportedOSDialog}
|
||||||
|
renderUpdateDialog={renderUpdateDialog}
|
||||||
|
savePreferredLeftPaneWidth={savePreferredLeftPaneWidth}
|
||||||
|
searchInConversation={searchInConversation}
|
||||||
|
selectedConversationId={selectedConversationId}
|
||||||
|
setChallengeStatus={setChallengeStatus}
|
||||||
|
setComposeGroupAvatar={setComposeGroupAvatar}
|
||||||
|
setComposeGroupExpireTimer={setComposeGroupExpireTimer}
|
||||||
|
setComposeGroupName={setComposeGroupName}
|
||||||
|
setComposeSearchTerm={setComposeSearchTerm}
|
||||||
|
setComposeSelectedRegion={setComposeSelectedRegion}
|
||||||
|
setIsFetchingUUID={setIsFetchingUUID}
|
||||||
|
showArchivedConversations={showArchivedConversations}
|
||||||
|
showChooseGroupMembers={showChooseGroupMembers}
|
||||||
|
showConversation={showConversation}
|
||||||
|
showFindByPhoneNumber={showFindByPhoneNumber}
|
||||||
|
showFindByUsername={showFindByUsername}
|
||||||
|
showInbox={showInbox}
|
||||||
|
showUserNotFoundModal={showUserNotFoundModal}
|
||||||
|
startComposing={startComposing}
|
||||||
|
startSearch={startSearch}
|
||||||
|
startSettingGroupMetadata={startSettingGroupMetadata}
|
||||||
|
targetedMessageId={targetedMessageId}
|
||||||
|
theme={theme}
|
||||||
|
toggleComposeEditingAvatar={toggleComposeEditingAvatar}
|
||||||
|
toggleConversationInChooseMembers={toggleConversationInChooseMembers}
|
||||||
|
toggleNavTabsCollapse={toggleNavTabsCollapse}
|
||||||
|
toggleProfileEditor={toggleProfileEditor}
|
||||||
|
unsupportedOSDialogType={unsupportedOSDialogType}
|
||||||
|
updateSearchTerm={updateSearchTerm}
|
||||||
|
usernameCorrupted={usernameCorrupted}
|
||||||
|
usernameLinkCorrupted={usernameLinkCorrupted}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
@ -1,14 +1,8 @@
|
||||||
// Copyright 2022 Signal Messenger, LLC
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React, { useCallback } from 'react';
|
import React, { memo, useCallback } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import type { ReadonlyDeep } from 'type-fest';
|
|
||||||
import type { GetConversationByIdType } from '../selectors/conversations';
|
|
||||||
import type { LocalizerType } from '../../types/Util';
|
|
||||||
import type { MediaItemType } from '../../types/MediaItem';
|
|
||||||
import type { StateType } from '../reducer';
|
|
||||||
import { Lightbox } from '../../components/Lightbox';
|
import { Lightbox } from '../../components/Lightbox';
|
||||||
import { getConversationSelector } from '../selectors/conversations';
|
import { getConversationSelector } from '../selectors/conversations';
|
||||||
import { getIntl } from '../selectors/user';
|
import { getIntl } from '../selectors/user';
|
||||||
|
@ -26,8 +20,8 @@ import {
|
||||||
shouldShowLightbox,
|
shouldShowLightbox,
|
||||||
} from '../selectors/lightbox';
|
} from '../selectors/lightbox';
|
||||||
|
|
||||||
export function SmartLightbox(): JSX.Element | null {
|
export const SmartLightbox = memo(function SmartLightbox() {
|
||||||
const i18n = useSelector<StateType, LocalizerType>(getIntl);
|
const i18n = useSelector(getIntl);
|
||||||
const { saveAttachment } = useConversationsActions();
|
const { saveAttachment } = useConversationsActions();
|
||||||
const {
|
const {
|
||||||
closeLightbox,
|
closeLightbox,
|
||||||
|
@ -38,20 +32,15 @@ export function SmartLightbox(): JSX.Element | null {
|
||||||
const { toggleForwardMessagesModal } = useGlobalModalActions();
|
const { toggleForwardMessagesModal } = useGlobalModalActions();
|
||||||
const { pauseVoiceNotePlayer } = useAudioPlayerActions();
|
const { pauseVoiceNotePlayer } = useAudioPlayerActions();
|
||||||
|
|
||||||
const conversationSelector = useSelector<StateType, GetConversationByIdType>(
|
const conversationSelector = useSelector(getConversationSelector);
|
||||||
getConversationSelector
|
|
||||||
);
|
|
||||||
|
|
||||||
const isShowingLightbox = useSelector<StateType, boolean>(shouldShowLightbox);
|
const isShowingLightbox = useSelector(shouldShowLightbox);
|
||||||
const isViewOnce = useSelector<StateType, boolean>(getIsViewOnce);
|
const isViewOnce = useSelector(getIsViewOnce);
|
||||||
const media = useSelector<
|
const media = useSelector(getMedia);
|
||||||
StateType,
|
const hasPrevMessage = useSelector(getHasPrevMessage);
|
||||||
ReadonlyArray<ReadonlyDeep<MediaItemType>>
|
const hasNextMessage = useSelector(getHasNextMessage);
|
||||||
>(getMedia);
|
const selectedIndex = useSelector(getSelectedIndex);
|
||||||
const hasPrevMessage = useSelector<StateType, boolean>(getHasPrevMessage);
|
const playbackDisabled = useSelector(getPlaybackDisabled);
|
||||||
const hasNextMessage = useSelector<StateType, boolean>(getHasNextMessage);
|
|
||||||
const selectedIndex = useSelector<StateType, number>(getSelectedIndex);
|
|
||||||
const playbackDisabled = useSelector<StateType, boolean>(getPlaybackDisabled);
|
|
||||||
|
|
||||||
const onPrevAttachment = useCallback(() => {
|
const onPrevAttachment = useCallback(() => {
|
||||||
if (selectedIndex <= 0) {
|
if (selectedIndex <= 0) {
|
||||||
|
@ -107,4 +96,4 @@ export function SmartLightbox(): JSX.Element | null {
|
||||||
hasPrevMessage={hasPrevMessage}
|
hasPrevMessage={hasPrevMessage}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import React, { useCallback } from 'react';
|
import React, { memo, useCallback } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { MessageAudio } from '../../components/conversation/MessageAudio';
|
import { MessageAudio } from '../../components/conversation/MessageAudio';
|
||||||
|
@ -26,10 +26,10 @@ export type Props = Omit<MessageAudioOwnProps, 'active' | 'onPlayMessage'> & {
|
||||||
renderingContext: string;
|
renderingContext: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function SmartMessageAudio({
|
export const SmartMessageAudio = memo(function SmartMessageAudio({
|
||||||
renderingContext,
|
renderingContext,
|
||||||
...props
|
...props
|
||||||
}: Props): JSX.Element | null {
|
}: Props) {
|
||||||
const active = useSelector(selectAudioPlayerActive);
|
const active = useSelector(selectAudioPlayerActive);
|
||||||
const { loadVoiceNoteAudio, setIsPlaying, setPlaybackRate, setPosition } =
|
const { loadVoiceNoteAudio, setIsPlaying, setPlaybackRate, setPosition } =
|
||||||
useAudioPlayerActions();
|
useAudioPlayerActions();
|
||||||
|
@ -100,4 +100,4 @@ export function SmartMessageAudio({
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React, { useEffect } from 'react';
|
import React, { memo, useEffect } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import type { Props as MessageDetailProps } from '../../components/conversation/MessageDetail';
|
import type { Props as MessageDetailProps } from '../../components/conversation/MessageDetail';
|
||||||
|
@ -28,7 +28,8 @@ export type OwnProps = Pick<
|
||||||
'contacts' | 'errors' | 'message' | 'receivedAt'
|
'contacts' | 'errors' | 'message' | 'receivedAt'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function SmartMessageDetail(): JSX.Element | null {
|
export const SmartMessageDetail = memo(
|
||||||
|
function SmartMessageDetail(): JSX.Element | null {
|
||||||
const getContactNameColor = useSelector(getContactNameColorSelector);
|
const getContactNameColor = useSelector(getContactNameColorSelector);
|
||||||
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||||
const i18n = useSelector(getIntl);
|
const i18n = useSelector(getIntl);
|
||||||
|
@ -113,4 +114,5 @@ export function SmartMessageDetail(): JSX.Element | null {
|
||||||
viewStory={viewStory}
|
viewStory={viewStory}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
// Copyright 2024 Signal Messenger, LLC
|
// Copyright 2024 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React, { useCallback, useMemo } from 'react';
|
import React, { memo, useCallback, useMemo } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { getIntl } from '../selectors/user';
|
import { getIntl } from '../selectors/user';
|
||||||
import { getGlobalModalsState } from '../selectors/globalModals';
|
import { getGlobalModalsState } from '../selectors/globalModals';
|
||||||
import { getConversationSelector } from '../selectors/conversations';
|
import { getConversationSelector } from '../selectors/conversations';
|
||||||
|
@ -17,7 +16,8 @@ import { getAddedByForOurPendingInvitation } from '../../util/getAddedByForOurPe
|
||||||
import { strictAssert } from '../../util/assert';
|
import { strictAssert } from '../../util/assert';
|
||||||
import { useGlobalModalActions } from '../ducks/globalModals';
|
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||||
|
|
||||||
export function SmartMessageRequestActionsConfirmation(): JSX.Element | null {
|
export const SmartMessageRequestActionsConfirmation = memo(
|
||||||
|
function SmartMessageRequestActionsConfirmation() {
|
||||||
const i18n = useSelector(getIntl);
|
const i18n = useSelector(getIntl);
|
||||||
const globalModals = useSelector(getGlobalModalsState);
|
const globalModals = useSelector(getGlobalModalsState);
|
||||||
const { messageRequestActionsConfirmationProps } = globalModals;
|
const { messageRequestActionsConfirmationProps } = globalModals;
|
||||||
|
@ -81,4 +81,5 @@ export function SmartMessageRequestActionsConfirmation(): JSX.Element | null {
|
||||||
onChangeState={handleChangeState}
|
onChangeState={handleChangeState}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
|
@ -1,40 +1,51 @@
|
||||||
// Copyright 2019 Signal Messenger, LLC
|
// Copyright 2019 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-onlyå
|
||||||
|
import React, { memo } from 'react';
|
||||||
import type { CSSProperties } from 'react';
|
import { useSelector } from 'react-redux';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import { mapDispatchToProps } from '../actions';
|
|
||||||
import type { StateType } from '../reducer';
|
|
||||||
|
|
||||||
import { MessageSearchResult } from '../../components/conversationList/MessageSearchResult';
|
import { MessageSearchResult } from '../../components/conversationList/MessageSearchResult';
|
||||||
import { getPreferredBadgeSelector } from '../selectors/badges';
|
import { getPreferredBadgeSelector } from '../selectors/badges';
|
||||||
import { getIntl, getTheme } from '../selectors/user';
|
import { getIntl, getTheme } from '../selectors/user';
|
||||||
import { getMessageSearchResultSelector } from '../selectors/search';
|
import { getMessageSearchResultSelector } from '../selectors/search';
|
||||||
import * as log from '../../logging/log';
|
import * as log from '../../logging/log';
|
||||||
|
import { useConversationsActions } from '../ducks/conversations';
|
||||||
|
|
||||||
type SmartProps = {
|
type SmartMessageSearchResultProps = {
|
||||||
id: string;
|
id: string;
|
||||||
style?: CSSProperties;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function mapStateToProps(state: StateType, ourProps: SmartProps) {
|
export const SmartMessageSearchResult = memo(function SmartMessageSearchResult({
|
||||||
const { id, style } = ourProps;
|
id,
|
||||||
|
}: SmartMessageSearchResultProps) {
|
||||||
|
const i18n = useSelector(getIntl);
|
||||||
|
const theme = useSelector(getTheme);
|
||||||
|
const messageSearchResultSelector = useSelector(
|
||||||
|
getMessageSearchResultSelector
|
||||||
|
);
|
||||||
|
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||||
|
const { showConversation } = useConversationsActions();
|
||||||
|
|
||||||
const props = getMessageSearchResultSelector(state)(id);
|
const messageResult = messageSearchResultSelector(id);
|
||||||
if (!props) {
|
if (messageResult == null) {
|
||||||
log.error('SmartMessageSearchResult: no message was found');
|
log.error('SmartMessageSearchResult: no message was found');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
const { conversationId, snippet, body, bodyRanges, from, to, sentAt } =
|
||||||
|
messageResult;
|
||||||
|
|
||||||
return {
|
return (
|
||||||
...props,
|
<MessageSearchResult
|
||||||
getPreferredBadge: getPreferredBadgeSelector(state),
|
i18n={i18n}
|
||||||
i18n: getIntl(state),
|
theme={theme}
|
||||||
style,
|
getPreferredBadge={getPreferredBadge}
|
||||||
theme: getTheme(state),
|
id={id}
|
||||||
};
|
conversationId={conversationId}
|
||||||
}
|
snippet={snippet}
|
||||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
body={body}
|
||||||
|
bodyRanges={bodyRanges}
|
||||||
export const SmartMessageSearchResult = smart(MessageSearchResult);
|
from={from}
|
||||||
|
to={to}
|
||||||
|
showConversation={showConversation}
|
||||||
|
sentAt={sentAt}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright 2022 Signal Messenger, LLC
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React, { useCallback } from 'react';
|
import React, { memo, useCallback } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { MiniPlayer, PlayerState } from '../../components/MiniPlayer';
|
import { MiniPlayer, PlayerState } from '../../components/MiniPlayer';
|
||||||
import type { Props as DumbProps } from '../../components/MiniPlayer';
|
import type { Props as DumbProps } from '../../components/MiniPlayer';
|
||||||
|
@ -23,7 +23,9 @@ type Props = Pick<DumbProps, 'shouldFlow'>;
|
||||||
* It also triggers side-effecting actions (actual playback) in response to changes in
|
* It also triggers side-effecting actions (actual playback) in response to changes in
|
||||||
* the state
|
* the state
|
||||||
*/
|
*/
|
||||||
export function SmartMiniPlayer({ shouldFlow }: Props): JSX.Element | null {
|
export const SmartMiniPlayer = memo(function SmartMiniPlayer({
|
||||||
|
shouldFlow,
|
||||||
|
}: Props): JSX.Element | null {
|
||||||
const i18n = useSelector(getIntl);
|
const i18n = useSelector(getIntl);
|
||||||
const active = useSelector(selectAudioPlayerActive);
|
const active = useSelector(selectAudioPlayerActive);
|
||||||
const getVoiceNoteTitle = useSelector(selectVoiceNoteTitle);
|
const getVoiceNoteTitle = useSelector(selectVoiceNoteTitle);
|
||||||
|
@ -66,4 +68,4 @@ export function SmartMiniPlayer({ shouldFlow }: Props): JSX.Element | null {
|
||||||
playbackRate={active.playbackRate}
|
playbackRate={active.playbackRate}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright 2023 Signal Messenger, LLC
|
// Copyright 2023 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React, { useCallback } from 'react';
|
import React, { memo, useCallback } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import type { NavTabPanelProps } from '../../components/NavTabs';
|
import type { NavTabPanelProps } from '../../components/NavTabs';
|
||||||
import { NavTabs } from '../../components/NavTabs';
|
import { NavTabs } from '../../components/NavTabs';
|
||||||
|
@ -33,7 +33,7 @@ export type SmartNavTabsProps = Readonly<{
|
||||||
renderStoriesTab(props: NavTabPanelProps): JSX.Element;
|
renderStoriesTab(props: NavTabPanelProps): JSX.Element;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export function SmartNavTabs({
|
export const SmartNavTabs = memo(function SmartNavTabs({
|
||||||
navTabsCollapsed,
|
navTabsCollapsed,
|
||||||
onToggleNavTabsCollapse,
|
onToggleNavTabsCollapse,
|
||||||
renderCallsTab,
|
renderCallsTab,
|
||||||
|
@ -91,4 +91,4 @@ export function SmartNavTabs({
|
||||||
unreadStoriesCount={unreadStoriesCount}
|
unreadStoriesCount={unreadStoriesCount}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
|
@ -1,23 +1,37 @@
|
||||||
// Copyright 2020 Signal Messenger, LLC
|
// Copyright 2020 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
import React, { memo } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { mapDispatchToProps } from '../actions';
|
|
||||||
import { DialogNetworkStatus } from '../../components/DialogNetworkStatus';
|
import { DialogNetworkStatus } from '../../components/DialogNetworkStatus';
|
||||||
import type { StateType } from '../reducer';
|
|
||||||
import { getIntl } from '../selectors/user';
|
import { getIntl } from '../selectors/user';
|
||||||
import type { WidthBreakpoint } from '../../components/_util';
|
import type { WidthBreakpoint } from '../../components/_util';
|
||||||
|
import {
|
||||||
|
getNetworkIsOnline,
|
||||||
|
getNetworkIsOutage,
|
||||||
|
getNetworkSocketStatus,
|
||||||
|
} from '../selectors/network';
|
||||||
|
import { useUserActions } from '../ducks/user';
|
||||||
|
|
||||||
type PropsType = Readonly<{ containerWidthBreakpoint: WidthBreakpoint }>;
|
type SmartNetworkStatusProps = Readonly<{
|
||||||
|
containerWidthBreakpoint: WidthBreakpoint;
|
||||||
|
}>;
|
||||||
|
|
||||||
const mapStateToProps = (state: StateType, ownProps: PropsType) => {
|
export const SmartNetworkStatus = memo(function SmartNetworkStatus({
|
||||||
return {
|
containerWidthBreakpoint,
|
||||||
...state.network,
|
}: SmartNetworkStatusProps) {
|
||||||
i18n: getIntl(state),
|
const i18n = useSelector(getIntl);
|
||||||
...ownProps,
|
const isOnline = useSelector(getNetworkIsOnline);
|
||||||
};
|
const isOutage = useSelector(getNetworkIsOutage);
|
||||||
};
|
const socketStatus = useSelector(getNetworkSocketStatus);
|
||||||
|
const { manualReconnect } = useUserActions();
|
||||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
return (
|
||||||
|
<DialogNetworkStatus
|
||||||
export const SmartNetworkStatus = smart(DialogNetworkStatus);
|
containerWidthBreakpoint={containerWidthBreakpoint}
|
||||||
|
i18n={i18n}
|
||||||
|
isOnline={isOnline}
|
||||||
|
isOutage={isOutage}
|
||||||
|
socketStatus={socketStatus}
|
||||||
|
manualReconnect={manualReconnect}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
@ -1,12 +1,8 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
import React, { memo } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { mapDispatchToProps } from '../actions';
|
|
||||||
import type { PropsDataType } from '../../components/conversation/conversation-details/PendingInvites';
|
|
||||||
import { PendingInvites } from '../../components/conversation/conversation-details/PendingInvites';
|
import { PendingInvites } from '../../components/conversation/conversation-details/PendingInvites';
|
||||||
import type { StateType } from '../reducer';
|
|
||||||
|
|
||||||
import { getIntl, getTheme } from '../selectors/user';
|
import { getIntl, getTheme } from '../selectors/user';
|
||||||
import { getPreferredBadgeSelector } from '../selectors/badges';
|
import { getPreferredBadgeSelector } from '../selectors/badges';
|
||||||
import {
|
import {
|
||||||
|
@ -16,36 +12,48 @@ import {
|
||||||
import { getGroupMemberships } from '../../util/getGroupMemberships';
|
import { getGroupMemberships } from '../../util/getGroupMemberships';
|
||||||
import { assertDev } from '../../util/assert';
|
import { assertDev } from '../../util/assert';
|
||||||
import type { AciString } from '../../types/ServiceId';
|
import type { AciString } from '../../types/ServiceId';
|
||||||
|
import { useConversationsActions } from '../ducks/conversations';
|
||||||
|
|
||||||
export type SmartPendingInvitesProps = {
|
export type SmartPendingInvitesProps = {
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
ourAci: AciString;
|
ourAci: AciString;
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (
|
export const SmartPendingInvites = memo(function SmartPendingInvites({
|
||||||
state: StateType,
|
conversationId,
|
||||||
props: SmartPendingInvitesProps
|
ourAci,
|
||||||
): PropsDataType => {
|
}: SmartPendingInvitesProps) {
|
||||||
const conversationSelector = getConversationByIdSelector(state);
|
const i18n = useSelector(getIntl);
|
||||||
const conversationByServiceIdSelector =
|
const theme = useSelector(getTheme);
|
||||||
getConversationByServiceIdSelector(state);
|
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||||
|
const conversationSelector = useSelector(getConversationByIdSelector);
|
||||||
const conversation = conversationSelector(props.conversationId);
|
const conversationByServiceIdSelector = useSelector(
|
||||||
|
getConversationByServiceIdSelector
|
||||||
|
);
|
||||||
|
const conversation = conversationSelector(conversationId);
|
||||||
assertDev(
|
assertDev(
|
||||||
conversation,
|
conversation,
|
||||||
'<SmartPendingInvites> expected a conversation to be found'
|
'<SmartPendingInvites> expected a conversation to be found'
|
||||||
);
|
);
|
||||||
|
const groupMemberships = getGroupMemberships(
|
||||||
return {
|
|
||||||
...props,
|
|
||||||
...getGroupMemberships(conversation, conversationByServiceIdSelector),
|
|
||||||
conversation,
|
conversation,
|
||||||
getPreferredBadge: getPreferredBadgeSelector(state),
|
conversationByServiceIdSelector
|
||||||
i18n: getIntl(state),
|
);
|
||||||
theme: getTheme(state),
|
const {
|
||||||
};
|
approvePendingMembershipFromGroupV2,
|
||||||
};
|
revokePendingMembershipsFromGroupV2,
|
||||||
|
} = useConversationsActions();
|
||||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
return (
|
||||||
|
<PendingInvites
|
||||||
export const SmartPendingInvites = smart(PendingInvites);
|
i18n={i18n}
|
||||||
|
theme={theme}
|
||||||
|
getPreferredBadge={getPreferredBadge}
|
||||||
|
conversation={conversation}
|
||||||
|
ourAci={ourAci}
|
||||||
|
pendingMemberships={groupMemberships.pendingMemberships}
|
||||||
|
pendingApprovalMemberships={groupMemberships.pendingApprovalMemberships}
|
||||||
|
approvePendingMembershipFromGroupV2={approvePendingMembershipFromGroupV2}
|
||||||
|
revokePendingMembershipsFromGroupV2={revokePendingMembershipsFromGroupV2}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
@ -1,93 +1,130 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
import React, { memo } from 'react';
|
||||||
import React from 'react';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { mapDispatchToProps } from '../actions';
|
|
||||||
import type { PropsDataType as ProfileEditorModalPropsType } from '../../components/ProfileEditorModal';
|
|
||||||
import { ProfileEditorModal } from '../../components/ProfileEditorModal';
|
import { ProfileEditorModal } from '../../components/ProfileEditorModal';
|
||||||
import type { PropsDataType } from '../../components/ProfileEditor';
|
import { useConversationsActions } from '../ducks/conversations';
|
||||||
import { SmartEditUsernameModalBody } from './EditUsernameModalBody';
|
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||||
import type { StateType } from '../reducer';
|
import { useItemsActions } from '../ducks/items';
|
||||||
import { getIntl } from '../selectors/user';
|
import { useToastActions } from '../ducks/toast';
|
||||||
|
import { useUsernameActions } from '../ducks/username';
|
||||||
|
import { getMe } from '../selectors/conversations';
|
||||||
|
import { selectRecentEmojis } from '../selectors/emojis';
|
||||||
|
import {
|
||||||
|
getProfileEditorHasError,
|
||||||
|
getProfileEditorInitialEditState,
|
||||||
|
} from '../selectors/globalModals';
|
||||||
import {
|
import {
|
||||||
getEmojiSkinTone,
|
getEmojiSkinTone,
|
||||||
getHasCompletedUsernameLinkOnboarding,
|
getHasCompletedUsernameLinkOnboarding,
|
||||||
getUsernameCorrupted,
|
getUsernameCorrupted,
|
||||||
getUsernameLinkColor,
|
|
||||||
getUsernameLink,
|
getUsernameLink,
|
||||||
|
getUsernameLinkColor,
|
||||||
getUsernameLinkCorrupted,
|
getUsernameLinkCorrupted,
|
||||||
isInternalUser,
|
isInternalUser,
|
||||||
} from '../selectors/items';
|
} from '../selectors/items';
|
||||||
import { getMe } from '../selectors/conversations';
|
import { getIntl } from '../selectors/user';
|
||||||
import { selectRecentEmojis } from '../selectors/emojis';
|
|
||||||
import {
|
import {
|
||||||
getUsernameEditState,
|
getUsernameEditState,
|
||||||
getUsernameLinkState,
|
getUsernameLinkState,
|
||||||
} from '../selectors/username';
|
} from '../selectors/username';
|
||||||
|
import type { SmartEditUsernameModalBodyProps } from './EditUsernameModalBody';
|
||||||
|
import { SmartEditUsernameModalBody } from './EditUsernameModalBody';
|
||||||
|
|
||||||
function renderEditUsernameModalBody(props: {
|
function renderEditUsernameModalBody(
|
||||||
isRootModal: boolean;
|
props: SmartEditUsernameModalBodyProps
|
||||||
onClose: () => void;
|
): JSX.Element {
|
||||||
}): JSX.Element {
|
|
||||||
return <SmartEditUsernameModalBody {...props} />;
|
return <SmartEditUsernameModalBody {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps(
|
export const SmartProfileEditorModal = memo(function SmartProfileEditorModal() {
|
||||||
state: StateType
|
const i18n = useSelector(getIntl);
|
||||||
): Omit<PropsDataType, 'onEditStateChange' | 'onProfileChanged'> &
|
|
||||||
ProfileEditorModalPropsType {
|
|
||||||
const {
|
const {
|
||||||
profileAvatarPath,
|
aboutEmoji,
|
||||||
|
aboutText,
|
||||||
avatars: userAvatarData = [],
|
avatars: userAvatarData = [],
|
||||||
aboutText,
|
|
||||||
aboutEmoji,
|
|
||||||
color,
|
color,
|
||||||
|
familyName,
|
||||||
firstName,
|
firstName,
|
||||||
familyName,
|
|
||||||
id: conversationId,
|
id: conversationId,
|
||||||
username,
|
|
||||||
} = getMe(state);
|
|
||||||
const recentEmojis = selectRecentEmojis(state);
|
|
||||||
const skinTone = getEmojiSkinTone(state);
|
|
||||||
const hasCompletedUsernameLinkOnboarding =
|
|
||||||
getHasCompletedUsernameLinkOnboarding(state);
|
|
||||||
const usernameEditState = getUsernameEditState(state);
|
|
||||||
const usernameLinkState = getUsernameLinkState(state);
|
|
||||||
const usernameLinkColor = getUsernameLinkColor(state);
|
|
||||||
const usernameLink = getUsernameLink(state);
|
|
||||||
const usernameCorrupted = getUsernameCorrupted(state);
|
|
||||||
const usernameLinkCorrupted = getUsernameLinkCorrupted(state);
|
|
||||||
|
|
||||||
return {
|
|
||||||
aboutEmoji,
|
|
||||||
aboutText,
|
|
||||||
profileAvatarPath,
|
profileAvatarPath,
|
||||||
color,
|
|
||||||
conversationId,
|
|
||||||
familyName,
|
|
||||||
firstName: String(firstName),
|
|
||||||
hasCompletedUsernameLinkOnboarding,
|
|
||||||
hasError: state.globalModals.profileEditorHasError,
|
|
||||||
initialEditState: state.globalModals.profileEditorInitialEditState,
|
|
||||||
i18n: getIntl(state),
|
|
||||||
recentEmojis,
|
|
||||||
skinTone,
|
|
||||||
userAvatarData,
|
|
||||||
username,
|
username,
|
||||||
usernameCorrupted,
|
} = useSelector(getMe);
|
||||||
usernameEditState,
|
const hasCompletedUsernameLinkOnboarding = useSelector(
|
||||||
usernameLinkState,
|
getHasCompletedUsernameLinkOnboarding
|
||||||
usernameLinkColor,
|
);
|
||||||
usernameLinkCorrupted,
|
const hasError = useSelector(getProfileEditorHasError);
|
||||||
usernameLink,
|
const initialEditState = useSelector(getProfileEditorInitialEditState);
|
||||||
isUsernameDeletionEnabled: isInternalUser(state),
|
const isUsernameDeletionEnabled = useSelector(isInternalUser);
|
||||||
|
const recentEmojis = useSelector(selectRecentEmojis);
|
||||||
|
const skinTone = useSelector(getEmojiSkinTone);
|
||||||
|
const usernameCorrupted = useSelector(getUsernameCorrupted);
|
||||||
|
const usernameEditState = useSelector(getUsernameEditState);
|
||||||
|
const usernameLink = useSelector(getUsernameLink);
|
||||||
|
const usernameLinkColor = useSelector(getUsernameLinkColor);
|
||||||
|
const usernameLinkCorrupted = useSelector(getUsernameLinkCorrupted);
|
||||||
|
const usernameLinkState = useSelector(getUsernameLinkState);
|
||||||
|
|
||||||
renderEditUsernameModalBody,
|
const {
|
||||||
};
|
replaceAvatar,
|
||||||
}
|
saveAvatarToDisk,
|
||||||
|
saveAttachment,
|
||||||
|
deleteAvatarFromDisk,
|
||||||
|
myProfileChanged,
|
||||||
|
} = useConversationsActions();
|
||||||
|
const {
|
||||||
|
resetUsernameLink,
|
||||||
|
setUsernameLinkColor,
|
||||||
|
setUsernameEditState,
|
||||||
|
openUsernameReservationModal,
|
||||||
|
markCompletedUsernameLinkOnboarding,
|
||||||
|
deleteUsername,
|
||||||
|
} = useUsernameActions();
|
||||||
|
const { toggleProfileEditor, toggleProfileEditorHasError } =
|
||||||
|
useGlobalModalActions();
|
||||||
|
const { showToast } = useToastActions();
|
||||||
|
const { onSetSkinTone } = useItemsActions();
|
||||||
|
|
||||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
return (
|
||||||
|
<ProfileEditorModal
|
||||||
export const SmartProfileEditorModal = smart(ProfileEditorModal);
|
aboutEmoji={aboutEmoji}
|
||||||
|
aboutText={aboutText}
|
||||||
|
color={color}
|
||||||
|
conversationId={conversationId}
|
||||||
|
deleteAvatarFromDisk={deleteAvatarFromDisk}
|
||||||
|
deleteUsername={deleteUsername}
|
||||||
|
familyName={familyName}
|
||||||
|
firstName={firstName ?? ''}
|
||||||
|
hasCompletedUsernameLinkOnboarding={hasCompletedUsernameLinkOnboarding}
|
||||||
|
hasError={hasError}
|
||||||
|
i18n={i18n}
|
||||||
|
initialEditState={initialEditState}
|
||||||
|
isUsernameDeletionEnabled={isUsernameDeletionEnabled}
|
||||||
|
markCompletedUsernameLinkOnboarding={markCompletedUsernameLinkOnboarding}
|
||||||
|
myProfileChanged={myProfileChanged}
|
||||||
|
onSetSkinTone={onSetSkinTone}
|
||||||
|
openUsernameReservationModal={openUsernameReservationModal}
|
||||||
|
profileAvatarPath={profileAvatarPath}
|
||||||
|
recentEmojis={recentEmojis}
|
||||||
|
renderEditUsernameModalBody={renderEditUsernameModalBody}
|
||||||
|
replaceAvatar={replaceAvatar}
|
||||||
|
resetUsernameLink={resetUsernameLink}
|
||||||
|
saveAttachment={saveAttachment}
|
||||||
|
saveAvatarToDisk={saveAvatarToDisk}
|
||||||
|
setUsernameEditState={setUsernameEditState}
|
||||||
|
setUsernameLinkColor={setUsernameLinkColor}
|
||||||
|
showToast={showToast}
|
||||||
|
skinTone={skinTone}
|
||||||
|
toggleProfileEditor={toggleProfileEditor}
|
||||||
|
toggleProfileEditorHasError={toggleProfileEditorHasError}
|
||||||
|
userAvatarData={userAvatarData}
|
||||||
|
username={username}
|
||||||
|
usernameCorrupted={usernameCorrupted}
|
||||||
|
usernameEditState={usernameEditState}
|
||||||
|
usernameLink={usernameLink}
|
||||||
|
usernameLinkColor={usernameLinkColor}
|
||||||
|
usernameLinkCorrupted={usernameLinkCorrupted}
|
||||||
|
usernameLinkState={usernameLinkState}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
@ -1,16 +1,13 @@
|
||||||
// Copyright 2020 Signal Messenger, LLC
|
// Copyright 2020 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import * as React from 'react';
|
import type { Ref } from 'react';
|
||||||
|
import React, { forwardRef, memo } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import type { StateType } from '../reducer';
|
|
||||||
import { usePreferredReactionsActions } from '../ducks/preferredReactions';
|
import { usePreferredReactionsActions } from '../ducks/preferredReactions';
|
||||||
import { useItemsActions } from '../ducks/items';
|
import { useItemsActions } from '../ducks/items';
|
||||||
|
|
||||||
import { getIntl } from '../selectors/user';
|
import { getIntl } from '../selectors/user';
|
||||||
import { getPreferredReactionEmoji } from '../selectors/items';
|
import { getPreferredReactionEmoji } from '../selectors/items';
|
||||||
|
|
||||||
import type { LocalizerType } from '../../types/Util';
|
|
||||||
import type { Props as InternalProps } from '../../components/conversation/ReactionPicker';
|
import type { Props as InternalProps } from '../../components/conversation/ReactionPicker';
|
||||||
import { ReactionPicker } from '../../components/conversation/ReactionPicker';
|
import { ReactionPicker } from '../../components/conversation/ReactionPicker';
|
||||||
|
|
||||||
|
@ -24,20 +21,18 @@ type ExternalProps = Omit<
|
||||||
| 'skinTone'
|
| 'skinTone'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export const SmartReactionPicker = React.forwardRef<
|
export const SmartReactionPicker = memo(
|
||||||
HTMLDivElement,
|
forwardRef(function SmartReactionPickerInner(
|
||||||
ExternalProps
|
props: ExternalProps,
|
||||||
>(function SmartReactionPickerInner(props, ref) {
|
ref: Ref<HTMLDivElement>
|
||||||
|
) {
|
||||||
const { openCustomizePreferredReactionsModal } =
|
const { openCustomizePreferredReactionsModal } =
|
||||||
usePreferredReactionsActions();
|
usePreferredReactionsActions();
|
||||||
|
|
||||||
const { onSetSkinTone } = useItemsActions();
|
const { onSetSkinTone } = useItemsActions();
|
||||||
|
|
||||||
const i18n = useSelector<StateType, LocalizerType>(getIntl);
|
const i18n = useSelector(getIntl);
|
||||||
|
const preferredReactionEmoji = useSelector(getPreferredReactionEmoji);
|
||||||
const preferredReactionEmoji = useSelector<StateType, ReadonlyArray<string>>(
|
|
||||||
getPreferredReactionEmoji
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactionPicker
|
<ReactionPicker
|
||||||
|
@ -51,4 +46,5 @@ export const SmartReactionPicker = React.forwardRef<
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
})
|
||||||
|
);
|
||||||
|
|
|
@ -1,22 +1,26 @@
|
||||||
// Copyright 2020 Signal Messenger, LLC
|
// Copyright 2020 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
import React, { memo } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { mapDispatchToProps } from '../actions';
|
|
||||||
import { DialogRelink } from '../../components/DialogRelink';
|
import { DialogRelink } from '../../components/DialogRelink';
|
||||||
import type { StateType } from '../reducer';
|
|
||||||
import { getIntl } from '../selectors/user';
|
import { getIntl } from '../selectors/user';
|
||||||
import type { WidthBreakpoint } from '../../components/_util';
|
import type { WidthBreakpoint } from '../../components/_util';
|
||||||
|
import { useNetworkActions } from '../ducks/network';
|
||||||
|
|
||||||
type PropsType = Readonly<{ containerWidthBreakpoint: WidthBreakpoint }>;
|
type SmartRelinkDialogProps = Readonly<{
|
||||||
|
containerWidthBreakpoint: WidthBreakpoint;
|
||||||
|
}>;
|
||||||
|
|
||||||
const mapStateToProps = (state: StateType, ownProps: PropsType) => {
|
export const SmartRelinkDialog = memo(function SmartRelinkDialog({
|
||||||
return {
|
containerWidthBreakpoint,
|
||||||
i18n: getIntl(state),
|
}: SmartRelinkDialogProps) {
|
||||||
...ownProps,
|
const i18n = useSelector(getIntl);
|
||||||
};
|
const { relinkDevice } = useNetworkActions();
|
||||||
};
|
return (
|
||||||
|
<DialogRelink
|
||||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
i18n={i18n}
|
||||||
|
containerWidthBreakpoint={containerWidthBreakpoint}
|
||||||
export const SmartRelinkDialog = smart(DialogRelink);
|
relinkDevice={relinkDevice}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
@ -1,27 +1,39 @@
|
||||||
// Copyright 2020 Signal Messenger, LLC
|
// Copyright 2020 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
import React, { memo } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { mapDispatchToProps } from '../actions';
|
|
||||||
import { SafetyNumberModal } from '../../components/SafetyNumberModal';
|
import { SafetyNumberModal } from '../../components/SafetyNumberModal';
|
||||||
import type { StateType } from '../reducer';
|
import type { StateType } from '../reducer';
|
||||||
import { getContactSafetyNumber } from '../selectors/safetyNumber';
|
import { getContactSafetyNumber } from '../selectors/safetyNumber';
|
||||||
import { getConversationSelector } from '../selectors/conversations';
|
import { getConversationSelector } from '../selectors/conversations';
|
||||||
import { getIntl } from '../selectors/user';
|
import { getIntl } from '../selectors/user';
|
||||||
|
import { useSafetyNumberActions } from '../ducks/safetyNumber';
|
||||||
|
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||||
|
|
||||||
export type Props = {
|
export type SmartSafetyNumberModalProps = {
|
||||||
contactID: string;
|
contactID: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state: StateType, props: Props) => {
|
export const SmartSafetyNumberModal = memo(function SmartSafetyNumberModal({
|
||||||
return {
|
contactID,
|
||||||
...props,
|
}: SmartSafetyNumberModalProps) {
|
||||||
...getContactSafetyNumber(state, props),
|
const i18n = useSelector(getIntl);
|
||||||
contact: getConversationSelector(state)(props.contactID),
|
const conversationSelector = useSelector(getConversationSelector);
|
||||||
i18n: getIntl(state),
|
const contact = conversationSelector(contactID);
|
||||||
};
|
const contactSafetyNumber = useSelector((state: StateType) => {
|
||||||
};
|
return getContactSafetyNumber(state, { contactID });
|
||||||
|
});
|
||||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
const { generateSafetyNumber, toggleVerified } = useSafetyNumberActions();
|
||||||
|
const { toggleSafetyNumberModal } = useGlobalModalActions();
|
||||||
export const SmartSafetyNumberModal = smart(SafetyNumberModal);
|
return (
|
||||||
|
<SafetyNumberModal
|
||||||
|
i18n={i18n}
|
||||||
|
contact={contact}
|
||||||
|
safetyNumber={contactSafetyNumber.safetyNumber}
|
||||||
|
verificationDisabled={contactSafetyNumber.verificationDisabled}
|
||||||
|
toggleSafetyNumberModal={toggleSafetyNumberModal}
|
||||||
|
generateSafetyNumber={generateSafetyNumber}
|
||||||
|
toggleVerified={toggleVerified}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
@ -1,24 +1,38 @@
|
||||||
// Copyright 2020 Signal Messenger, LLC
|
// Copyright 2020 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { connect } from 'react-redux';
|
import React, { memo } from 'react';
|
||||||
import { mapDispatchToProps } from '../actions';
|
import { useSelector } from 'react-redux';
|
||||||
import { SafetyNumberViewer } from '../../components/SafetyNumberViewer';
|
import { SafetyNumberViewer } from '../../components/SafetyNumberViewer';
|
||||||
import type { StateType } from '../reducer';
|
import type { StateType } from '../reducer';
|
||||||
import type { SafetyNumberProps } from '../../components/SafetyNumberChangeDialog';
|
import type { SafetyNumberProps } from '../../components/SafetyNumberChangeDialog';
|
||||||
import { getContactSafetyNumber } from '../selectors/safetyNumber';
|
import { getContactSafetyNumber } from '../selectors/safetyNumber';
|
||||||
import { getConversationSelector } from '../selectors/conversations';
|
import { getConversationSelector } from '../selectors/conversations';
|
||||||
import { getIntl } from '../selectors/user';
|
import { getIntl } from '../selectors/user';
|
||||||
|
import { useSafetyNumberActions } from '../ducks/safetyNumber';
|
||||||
|
|
||||||
const mapStateToProps = (state: StateType, props: SafetyNumberProps) => {
|
export const SmartSafetyNumberViewer = memo(function SmartSafetyNumberViewer({
|
||||||
return {
|
contactID,
|
||||||
...props,
|
onClose,
|
||||||
...getContactSafetyNumber(state, props),
|
}: SafetyNumberProps) {
|
||||||
contact: getConversationSelector(state)(props.contactID),
|
const i18n = useSelector(getIntl);
|
||||||
i18n: getIntl(state),
|
const safetyNumberContact = useSelector((state: StateType) => {
|
||||||
};
|
return getContactSafetyNumber(state, { contactID });
|
||||||
};
|
});
|
||||||
|
const conversationSelector = useSelector(getConversationSelector);
|
||||||
|
const contact = conversationSelector(contactID);
|
||||||
|
|
||||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
const { generateSafetyNumber, toggleVerified } = useSafetyNumberActions();
|
||||||
|
|
||||||
export const SmartSafetyNumberViewer = smart(SafetyNumberViewer);
|
return (
|
||||||
|
<SafetyNumberViewer
|
||||||
|
contact={contact}
|
||||||
|
generateSafetyNumber={generateSafetyNumber}
|
||||||
|
i18n={i18n}
|
||||||
|
onClose={onClose}
|
||||||
|
safetyNumber={safetyNumberContact.safetyNumber}
|
||||||
|
toggleVerified={toggleVerified}
|
||||||
|
verificationDisabled={safetyNumberContact.verificationDisabled}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
// Copyright 2022 Signal Messenger, LLC
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React from 'react';
|
import React, { memo } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import type { LocalizerType } from '../../types/Util';
|
|
||||||
import type { SafetyNumberChangedBlockingDataType } from '../ducks/globalModals';
|
|
||||||
import type { StateType } from '../reducer';
|
|
||||||
import * as SingleServePromise from '../../services/singleServePromise';
|
import * as SingleServePromise from '../../services/singleServePromise';
|
||||||
import {
|
import {
|
||||||
SafetyNumberChangeDialog,
|
SafetyNumberChangeDialog,
|
||||||
|
@ -18,23 +15,26 @@ import { getPreferredBadgeSelector } from '../selectors/badges';
|
||||||
import { useConversationsActions } from '../ducks/conversations';
|
import { useConversationsActions } from '../ducks/conversations';
|
||||||
import { useGlobalModalActions } from '../ducks/globalModals';
|
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||||
import { useStoryDistributionListsActions } from '../ducks/storyDistributionLists';
|
import { useStoryDistributionListsActions } from '../ducks/storyDistributionLists';
|
||||||
|
import { getSafetyNumberChangedBlockingData } from '../selectors/globalModals';
|
||||||
|
|
||||||
export function SmartSendAnywayDialog(): JSX.Element {
|
export const SmartSendAnywayDialog = memo(
|
||||||
|
function SmartSendAnywayDialog(): JSX.Element {
|
||||||
const { hideBlockingSafetyNumberChangeDialog } = useGlobalModalActions();
|
const { hideBlockingSafetyNumberChangeDialog } = useGlobalModalActions();
|
||||||
const { removeMembersFromDistributionList } =
|
const { removeMembersFromDistributionList } =
|
||||||
useStoryDistributionListsActions();
|
useStoryDistributionListsActions();
|
||||||
const { cancelConversationVerification, verifyConversationsStoppingSend } =
|
const { cancelConversationVerification, verifyConversationsStoppingSend } =
|
||||||
useConversationsActions();
|
useConversationsActions();
|
||||||
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||||
const i18n = useSelector<StateType, LocalizerType>(getIntl);
|
const i18n = useSelector(getIntl);
|
||||||
const theme = useSelector(getTheme);
|
const theme = useSelector(getTheme);
|
||||||
|
|
||||||
const contacts = useSelector(getByDistributionListConversationsStoppingSend);
|
const contacts = useSelector(
|
||||||
|
getByDistributionListConversationsStoppingSend
|
||||||
|
);
|
||||||
|
|
||||||
const safetyNumberChangedBlockingData = useSelector<
|
const safetyNumberChangedBlockingData = useSelector(
|
||||||
StateType,
|
getSafetyNumberChangedBlockingData
|
||||||
SafetyNumberChangedBlockingDataType | undefined
|
);
|
||||||
>(state => state.globalModals.safetyNumberChangedBlockingData);
|
|
||||||
|
|
||||||
const explodedPromise = safetyNumberChangedBlockingData
|
const explodedPromise = safetyNumberChangedBlockingData
|
||||||
? SingleServePromise.get<boolean>(
|
? SingleServePromise.get<boolean>(
|
||||||
|
@ -82,4 +82,5 @@ export function SmartSendAnywayDialog(): JSX.Element {
|
||||||
theme={theme}
|
theme={theme}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
// Copyright 2019 Signal Messenger, LLC
|
// Copyright 2019 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { connect } from 'react-redux';
|
import React, { memo, useMemo } from 'react';
|
||||||
import { mapDispatchToProps } from '../actions';
|
import { useSelector } from 'react-redux';
|
||||||
import { ShortcutGuideModal } from '../../components/ShortcutGuideModal';
|
import { ShortcutGuideModal } from '../../components/ShortcutGuideModal';
|
||||||
import type { StateType } from '../reducer';
|
|
||||||
|
|
||||||
import { countStickers } from '../../components/stickers/lib';
|
import { countStickers } from '../../components/stickers/lib';
|
||||||
import { getIntl, getPlatform } from '../selectors/user';
|
import { getIntl, getPlatform } from '../selectors/user';
|
||||||
import {
|
import {
|
||||||
|
@ -14,30 +12,35 @@ import {
|
||||||
getKnownStickerPacks,
|
getKnownStickerPacks,
|
||||||
getReceivedStickerPacks,
|
getReceivedStickerPacks,
|
||||||
} from '../selectors/stickers';
|
} from '../selectors/stickers';
|
||||||
|
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||||
|
|
||||||
const mapStateToProps = (state: StateType) => {
|
export const SmartShortcutGuideModal = memo(function SmartShortcutGuideModal() {
|
||||||
const blessedPacks = getBlessedStickerPacks(state);
|
const i18n = useSelector(getIntl);
|
||||||
const installedPacks = getInstalledStickerPacks(state);
|
const blessedPacks = useSelector(getBlessedStickerPacks);
|
||||||
const knownPacks = getKnownStickerPacks(state);
|
const installedPacks = useSelector(getInstalledStickerPacks);
|
||||||
const receivedPacks = getReceivedStickerPacks(state);
|
const knownPacks = useSelector(getKnownStickerPacks);
|
||||||
|
const receivedPacks = useSelector(getReceivedStickerPacks);
|
||||||
|
const platform = useSelector(getPlatform);
|
||||||
|
|
||||||
const hasInstalledStickers =
|
const { closeShortcutGuideModal } = useGlobalModalActions();
|
||||||
|
|
||||||
|
const hasInstalledStickers = useMemo(() => {
|
||||||
|
return (
|
||||||
countStickers({
|
countStickers({
|
||||||
knownPacks,
|
knownPacks,
|
||||||
blessedPacks,
|
blessedPacks,
|
||||||
installedPacks,
|
installedPacks,
|
||||||
receivedPacks,
|
receivedPacks,
|
||||||
}) > 0;
|
}) > 0
|
||||||
|
);
|
||||||
|
}, [blessedPacks, installedPacks, knownPacks, receivedPacks]);
|
||||||
|
|
||||||
const platform = getPlatform(state);
|
return (
|
||||||
|
<ShortcutGuideModal
|
||||||
return {
|
hasInstalledStickers={hasInstalledStickers}
|
||||||
hasInstalledStickers,
|
platform={platform}
|
||||||
platform,
|
closeShortcutGuideModal={closeShortcutGuideModal}
|
||||||
i18n: getIntl(state),
|
i18n={i18n}
|
||||||
};
|
/>
|
||||||
};
|
);
|
||||||
|
});
|
||||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
|
||||||
|
|
||||||
export const SmartShortcutGuideModal = smart(ShortcutGuideModal);
|
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
// Copyright 2019 Signal Messenger, LLC
|
// Copyright 2019 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { connect } from 'react-redux';
|
import React, { memo } from 'react';
|
||||||
import { mapDispatchToProps } from '../actions';
|
import { useSelector } from 'react-redux';
|
||||||
import { StickerManager } from '../../components/stickers/StickerManager';
|
import { StickerManager } from '../../components/stickers/StickerManager';
|
||||||
import type { StateType } from '../reducer';
|
|
||||||
|
|
||||||
import { getIntl } from '../selectors/user';
|
import { getIntl } from '../selectors/user';
|
||||||
import {
|
import {
|
||||||
getBlessedStickerPacks,
|
getBlessedStickerPacks,
|
||||||
|
@ -13,22 +11,31 @@ import {
|
||||||
getKnownStickerPacks,
|
getKnownStickerPacks,
|
||||||
getReceivedStickerPacks,
|
getReceivedStickerPacks,
|
||||||
} from '../selectors/stickers';
|
} from '../selectors/stickers';
|
||||||
|
import { useStickersActions } from '../ducks/stickers';
|
||||||
|
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||||
|
|
||||||
const mapStateToProps = (state: StateType) => {
|
export const SmartStickerManager = memo(function SmartStickerManager() {
|
||||||
const blessedPacks = getBlessedStickerPacks(state);
|
const i18n = useSelector(getIntl);
|
||||||
const receivedPacks = getReceivedStickerPacks(state);
|
const blessedPacks = useSelector(getBlessedStickerPacks);
|
||||||
const installedPacks = getInstalledStickerPacks(state);
|
const receivedPacks = useSelector(getReceivedStickerPacks);
|
||||||
const knownPacks = getKnownStickerPacks(state);
|
const installedPacks = useSelector(getInstalledStickerPacks);
|
||||||
|
const knownPacks = useSelector(getKnownStickerPacks);
|
||||||
|
|
||||||
return {
|
const { downloadStickerPack, installStickerPack, uninstallStickerPack } =
|
||||||
blessedPacks,
|
useStickersActions();
|
||||||
receivedPacks,
|
const { closeStickerPackPreview } = useGlobalModalActions();
|
||||||
installedPacks,
|
|
||||||
knownPacks,
|
|
||||||
i18n: getIntl(state),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
return (
|
||||||
|
<StickerManager
|
||||||
export const SmartStickerManager = smart(StickerManager);
|
blessedPacks={blessedPacks}
|
||||||
|
closeStickerPackPreview={closeStickerPackPreview}
|
||||||
|
downloadStickerPack={downloadStickerPack}
|
||||||
|
i18n={i18n}
|
||||||
|
installStickerPack={installStickerPack}
|
||||||
|
installedPacks={installedPacks}
|
||||||
|
knownPacks={knownPacks}
|
||||||
|
receivedPacks={receivedPacks}
|
||||||
|
uninstallStickerPack={uninstallStickerPack}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
@ -1,40 +1,48 @@
|
||||||
// Copyright 2019 Signal Messenger, LLC
|
// Copyright 2019 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { connect } from 'react-redux';
|
import React, { memo } from 'react';
|
||||||
import { mapDispatchToProps } from '../actions';
|
import { useSelector } from 'react-redux';
|
||||||
import { StickerPreviewModal } from '../../components/stickers/StickerPreviewModal';
|
import { StickerPreviewModal } from '../../components/stickers/StickerPreviewModal';
|
||||||
import type { StateType } from '../reducer';
|
|
||||||
|
|
||||||
import { getIntl, getStickersPath, getTempPath } from '../selectors/user';
|
import { getIntl, getStickersPath, getTempPath } from '../selectors/user';
|
||||||
import {
|
import {
|
||||||
getBlessedPacks,
|
getBlessedPacks,
|
||||||
getPacks,
|
getPacks,
|
||||||
translatePackFromDB,
|
translatePackFromDB,
|
||||||
} from '../selectors/stickers';
|
} from '../selectors/stickers';
|
||||||
|
import { useStickersActions } from '../ducks/stickers';
|
||||||
|
import { useGlobalModalActions } from '../ducks/globalModals';
|
||||||
|
|
||||||
export type ExternalProps = {
|
export type ExternalProps = {
|
||||||
packId: string;
|
packId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state: StateType, props: ExternalProps) => {
|
export const SmartStickerPreviewModal = memo(function SmartStickerPreviewModal({
|
||||||
const { packId } = props;
|
packId,
|
||||||
const stickersPath = getStickersPath(state);
|
}: ExternalProps) {
|
||||||
const tempPath = getTempPath(state);
|
const i18n = useSelector(getIntl);
|
||||||
|
const packs = useSelector(getPacks);
|
||||||
|
const blessedPacks = useSelector(getBlessedPacks);
|
||||||
|
const stickersPath = useSelector(getStickersPath);
|
||||||
|
const tempPath = useSelector(getTempPath);
|
||||||
|
|
||||||
const packs = getPacks(state);
|
const { downloadStickerPack, installStickerPack, uninstallStickerPack } =
|
||||||
const blessedPacks = getBlessedPacks(state);
|
useStickersActions();
|
||||||
const pack = packs[packId];
|
const { closeStickerPackPreview } = useGlobalModalActions();
|
||||||
|
|
||||||
return {
|
const packDb = packs[packId];
|
||||||
...props,
|
const pack = packDb
|
||||||
pack: pack
|
? translatePackFromDB(packDb, packs, blessedPacks, stickersPath, tempPath)
|
||||||
? translatePackFromDB(pack, packs, blessedPacks, stickersPath, tempPath)
|
: undefined;
|
||||||
: undefined,
|
|
||||||
i18n: getIntl(state),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
return (
|
||||||
|
<StickerPreviewModal
|
||||||
export const SmartStickerPreviewModal = smart(StickerPreviewModal);
|
closeStickerPackPreview={closeStickerPackPreview}
|
||||||
|
downloadStickerPack={downloadStickerPack}
|
||||||
|
i18n={i18n}
|
||||||
|
installStickerPack={installStickerPack}
|
||||||
|
pack={pack}
|
||||||
|
uninstallStickerPack={uninstallStickerPack}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
// Copyright 2022 Signal Messenger, LLC
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React from 'react';
|
import React, { memo } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import type { LocalizerType } from '../../types/Util';
|
|
||||||
import type { StateType } from '../reducer';
|
|
||||||
import { StoriesSettingsModal } from '../../components/StoriesSettingsModal';
|
import { StoriesSettingsModal } from '../../components/StoriesSettingsModal';
|
||||||
import {
|
import {
|
||||||
getAllSignalConnections,
|
getAllSignalConnections,
|
||||||
|
@ -23,7 +20,8 @@ import { useStoryDistributionListsActions } from '../ducks/storyDistributionList
|
||||||
import { useStoriesActions } from '../ducks/stories';
|
import { useStoriesActions } from '../ducks/stories';
|
||||||
import { useConversationsActions } from '../ducks/conversations';
|
import { useConversationsActions } from '../ducks/conversations';
|
||||||
|
|
||||||
export function SmartStoriesSettingsModal(): JSX.Element | null {
|
export const SmartStoriesSettingsModal = memo(
|
||||||
|
function SmartStoriesSettingsModal() {
|
||||||
const { setStoriesDisabled } = useStoriesActions();
|
const { setStoriesDisabled } = useStoriesActions();
|
||||||
const { hideStoriesSettings, toggleSignalConnectionsModal } =
|
const { hideStoriesSettings, toggleSignalConnectionsModal } =
|
||||||
useGlobalModalActions();
|
useGlobalModalActions();
|
||||||
|
@ -37,13 +35,12 @@ export function SmartStoriesSettingsModal(): JSX.Element | null {
|
||||||
updateStoryViewers,
|
updateStoryViewers,
|
||||||
} = useStoryDistributionListsActions();
|
} = useStoryDistributionListsActions();
|
||||||
const { toggleGroupsForStorySend } = useConversationsActions();
|
const { toggleGroupsForStorySend } = useConversationsActions();
|
||||||
const signalConnections = useSelector(getAllSignalConnections);
|
|
||||||
|
|
||||||
|
const signalConnections = useSelector(getAllSignalConnections);
|
||||||
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||||
const storyViewReceiptsEnabled = useSelector(getHasStoryViewReceiptSetting);
|
const storyViewReceiptsEnabled = useSelector(getHasStoryViewReceiptSetting);
|
||||||
const i18n = useSelector<StateType, LocalizerType>(getIntl);
|
const i18n = useSelector(getIntl);
|
||||||
const me = useSelector(getMe);
|
const me = useSelector(getMe);
|
||||||
|
|
||||||
const candidateConversations = useSelector(getCandidateContactsForNewGroup);
|
const candidateConversations = useSelector(getCandidateContactsForNewGroup);
|
||||||
const distributionLists = useSelector(getDistributionListsWithMembers);
|
const distributionLists = useSelector(getDistributionListsWithMembers);
|
||||||
const groupStories = useSelector(getGroupStories);
|
const groupStories = useSelector(getGroupStories);
|
||||||
|
@ -78,4 +75,5 @@ export function SmartStoriesSettingsModal(): JSX.Element | null {
|
||||||
setStoriesDisabled={setStoriesDisabled}
|
setStoriesDisabled={setStoriesDisabled}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
// Copyright 2022 Signal Messenger, LLC
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React, { useEffect } from 'react';
|
import React, { memo, useEffect } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import type { LocalizerType } from '../../types/Util';
|
|
||||||
import type { StateType } from '../reducer';
|
|
||||||
import { SmartStoryCreator } from './StoryCreator';
|
import { SmartStoryCreator } from './StoryCreator';
|
||||||
import { SmartToastManager } from './ToastManager';
|
import { SmartToastManager } from './ToastManager';
|
||||||
import type { WidthBreakpoint } from '../../components/_util';
|
import type { WidthBreakpoint } from '../../components/_util';
|
||||||
|
@ -35,6 +32,7 @@ import { useAudioPlayerActions } from '../ducks/audioPlayer';
|
||||||
import { useItemsActions } from '../ducks/items';
|
import { useItemsActions } from '../ducks/items';
|
||||||
import { getHasPendingUpdate } from '../selectors/updates';
|
import { getHasPendingUpdate } from '../selectors/updates';
|
||||||
import { getOtherTabsUnreadStats } from '../selectors/nav';
|
import { getOtherTabsUnreadStats } from '../selectors/nav';
|
||||||
|
import { getIsStoriesSettingsVisible } from '../selectors/globalModals';
|
||||||
|
|
||||||
function renderStoryCreator(): JSX.Element {
|
function renderStoryCreator(): JSX.Element {
|
||||||
return <SmartStoryCreator />;
|
return <SmartStoryCreator />;
|
||||||
|
@ -46,7 +44,7 @@ function renderToastManager(props: {
|
||||||
return <SmartToastManager disableMegaphone {...props} />;
|
return <SmartToastManager disableMegaphone {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SmartStoriesTab(): JSX.Element | null {
|
export const SmartStoriesTab = memo(function SmartStoriesTab() {
|
||||||
const storiesActions = useStoriesActions();
|
const storiesActions = useStoriesActions();
|
||||||
const {
|
const {
|
||||||
retryMessageSend,
|
retryMessageSend,
|
||||||
|
@ -58,30 +56,20 @@ export function SmartStoriesTab(): JSX.Element | null {
|
||||||
useGlobalModalActions();
|
useGlobalModalActions();
|
||||||
const { showToast } = useToastActions();
|
const { showToast } = useToastActions();
|
||||||
|
|
||||||
const i18n = useSelector<StateType, LocalizerType>(getIntl);
|
const i18n = useSelector(getIntl);
|
||||||
|
const preferredWidthFromStorage = useSelector(getPreferredLeftPaneWidth);
|
||||||
const preferredWidthFromStorage = useSelector<StateType, number>(
|
|
||||||
getPreferredLeftPaneWidth
|
|
||||||
);
|
|
||||||
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||||
|
|
||||||
const addStoryData = useSelector(getAddStoryData);
|
const addStoryData = useSelector(getAddStoryData);
|
||||||
const { hiddenStories, myStories, stories } = useSelector(getStories);
|
const { hiddenStories, myStories, stories } = useSelector(getStories);
|
||||||
|
|
||||||
const me = useSelector(getMe);
|
const me = useSelector(getMe);
|
||||||
|
|
||||||
const selectedStoryData = useSelector(getSelectedStoryData);
|
const selectedStoryData = useSelector(getSelectedStoryData);
|
||||||
|
const isStoriesSettingsVisible = useSelector(getIsStoriesSettingsVisible);
|
||||||
const isStoriesSettingsVisible = useSelector(
|
|
||||||
(state: StateType) => state.globalModals.isStoriesSettingsVisible
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasViewReceiptSetting = useSelector(getHasStoryViewReceiptSetting);
|
const hasViewReceiptSetting = useSelector(getHasStoryViewReceiptSetting);
|
||||||
const hasPendingUpdate = useSelector(getHasPendingUpdate);
|
const hasPendingUpdate = useSelector(getHasPendingUpdate);
|
||||||
const hasFailedStorySends = useSelector(getHasAnyFailedStorySends);
|
const hasFailedStorySends = useSelector(getHasAnyFailedStorySends);
|
||||||
const otherTabsUnreadStats = useSelector(getOtherTabsUnreadStats);
|
const otherTabsUnreadStats = useSelector(getOtherTabsUnreadStats);
|
||||||
|
|
||||||
const remoteConfig = useSelector(getRemoteConfig);
|
const remoteConfig = useSelector(getRemoteConfig);
|
||||||
|
|
||||||
const maxAttachmentSizeInKb = getMaximumOutgoingAttachmentSizeInKb(
|
const maxAttachmentSizeInKb = getMaximumOutgoingAttachmentSizeInKb(
|
||||||
(name: ConfigKeyType) => {
|
(name: ConfigKeyType) => {
|
||||||
const value = remoteConfig[name]?.value;
|
const value = remoteConfig[name]?.value;
|
||||||
|
@ -145,4 +133,4 @@ export function SmartStoriesTab(): JSX.Element | null {
|
||||||
{...storiesActions}
|
{...storiesActions}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
// Copyright 2022 Signal Messenger, LLC
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React from 'react';
|
import React, { memo } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
import { ThemeType } from '../../types/Util';
|
||||||
import { ThemeType, type LocalizerType } from '../../types/Util';
|
|
||||||
import type { StateType } from '../reducer';
|
|
||||||
import { LinkPreviewSourceType } from '../../types/LinkPreview';
|
import { LinkPreviewSourceType } from '../../types/LinkPreview';
|
||||||
import { StoryCreator } from '../../components/StoryCreator';
|
import { StoryCreator } from '../../components/StoryCreator';
|
||||||
import {
|
import {
|
||||||
|
@ -48,7 +46,7 @@ export type PropsType = {
|
||||||
onClose: () => unknown;
|
onClose: () => unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function SmartStoryCreator(): JSX.Element | null {
|
export const SmartStoryCreator = memo(function SmartStoryCreator() {
|
||||||
const { debouncedMaybeGrabLinkPreview } = useLinkPreviewActions();
|
const { debouncedMaybeGrabLinkPreview } = useLinkPreviewActions();
|
||||||
const {
|
const {
|
||||||
sendStoryModalOpenStateChanged,
|
sendStoryModalOpenStateChanged,
|
||||||
|
@ -75,7 +73,7 @@ export function SmartStoryCreator(): JSX.Element | null {
|
||||||
const groupConversations = useSelector(getNonGroupStories);
|
const groupConversations = useSelector(getNonGroupStories);
|
||||||
const groupStories = useSelector(getGroupStories);
|
const groupStories = useSelector(getGroupStories);
|
||||||
const hasSetMyStoriesPrivacy = useSelector(getHasSetMyStoriesPrivacy);
|
const hasSetMyStoriesPrivacy = useSelector(getHasSetMyStoriesPrivacy);
|
||||||
const i18n = useSelector<StateType, LocalizerType>(getIntl);
|
const i18n = useSelector(getIntl);
|
||||||
const installedPacks = useSelector(getInstalledStickerPacks);
|
const installedPacks = useSelector(getInstalledStickerPacks);
|
||||||
const linkPreviewForSource = useSelector(getLinkPreview);
|
const linkPreviewForSource = useSelector(getLinkPreview);
|
||||||
const me = useSelector(getMe);
|
const me = useSelector(getMe);
|
||||||
|
@ -96,7 +94,7 @@ export function SmartStoryCreator(): JSX.Element | null {
|
||||||
}
|
}
|
||||||
|
|
||||||
const recentEmojis = useRecentEmojis();
|
const recentEmojis = useRecentEmojis();
|
||||||
const skinTone = useSelector<StateType, number>(getEmojiSkinTone);
|
const skinTone = useSelector(getEmojiSkinTone);
|
||||||
const { onSetSkinTone } = useItemsActions();
|
const { onSetSkinTone } = useItemsActions();
|
||||||
const { onUseEmoji } = useEmojisActions();
|
const { onUseEmoji } = useEmojisActions();
|
||||||
const { pauseVoiceNotePlayer } = useAudioPlayerActions();
|
const { pauseVoiceNotePlayer } = useAudioPlayerActions();
|
||||||
|
@ -155,4 +153,4 @@ export function SmartStoryCreator(): JSX.Element | null {
|
||||||
toggleSignalConnectionsModal={toggleSignalConnectionsModal}
|
toggleSignalConnectionsModal={toggleSignalConnectionsModal}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
|
@ -1,13 +1,8 @@
|
||||||
// Copyright 2022 Signal Messenger, LLC
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React from 'react';
|
import React, { memo } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import type { GetConversationByIdType } from '../selectors/conversations';
|
|
||||||
import type { LocalizerType } from '../../types/Util';
|
|
||||||
import type { StateType } from '../reducer';
|
|
||||||
import type { SelectedStoryDataType } from '../ducks/stories';
|
|
||||||
import { StoryViewer } from '../../components/StoryViewer';
|
import { StoryViewer } from '../../components/StoryViewer';
|
||||||
import { ToastType } from '../../types/Toast';
|
import { ToastType } from '../../types/Toast';
|
||||||
import { useToastActions } from '../ducks/toast';
|
import { useToastActions } from '../ducks/toast';
|
||||||
|
@ -41,7 +36,7 @@ import { useGlobalModalActions } from '../ducks/globalModals';
|
||||||
import { useStoriesActions } from '../ducks/stories';
|
import { useStoriesActions } from '../ducks/stories';
|
||||||
import { useIsWindowActive } from '../../hooks/useIsWindowActive';
|
import { useIsWindowActive } from '../../hooks/useIsWindowActive';
|
||||||
|
|
||||||
export function SmartStoryViewer(): JSX.Element | null {
|
export const SmartStoryViewer = memo(function SmartStoryViewer() {
|
||||||
const storiesActions = useStoriesActions();
|
const storiesActions = useStoriesActions();
|
||||||
const { onUseEmoji } = useEmojisActions();
|
const { onUseEmoji } = useEmojisActions();
|
||||||
const {
|
const {
|
||||||
|
@ -56,40 +51,24 @@ export function SmartStoryViewer(): JSX.Element | null {
|
||||||
|
|
||||||
const isWindowActive = useIsWindowActive();
|
const isWindowActive = useIsWindowActive();
|
||||||
|
|
||||||
const i18n = useSelector<StateType, LocalizerType>(getIntl);
|
const i18n = useSelector(getIntl);
|
||||||
const platform = useSelector(getPlatform);
|
const platform = useSelector(getPlatform);
|
||||||
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||||
const preferredReactionEmoji = useSelector<StateType, ReadonlyArray<string>>(
|
const preferredReactionEmoji = useSelector(getPreferredReactionEmoji);
|
||||||
getPreferredReactionEmoji
|
const selectedStoryData = useSelector(getSelectedStoryData);
|
||||||
);
|
const internalUser = useSelector(isInternalUser);
|
||||||
|
|
||||||
const selectedStoryData = useSelector<
|
|
||||||
StateType,
|
|
||||||
SelectedStoryDataType | undefined
|
|
||||||
>(getSelectedStoryData);
|
|
||||||
|
|
||||||
const internalUser = useSelector<StateType, boolean>(isInternalUser);
|
|
||||||
|
|
||||||
strictAssert(selectedStoryData, 'StoryViewer: !selectedStoryData');
|
strictAssert(selectedStoryData, 'StoryViewer: !selectedStoryData');
|
||||||
|
|
||||||
const conversationSelector = useSelector<StateType, GetConversationByIdType>(
|
const conversationSelector = useSelector(getConversationSelector);
|
||||||
getConversationSelector
|
|
||||||
);
|
|
||||||
|
|
||||||
const getStoryById = useSelector(getStoryByIdSelector);
|
const getStoryById = useSelector(getStoryByIdSelector);
|
||||||
|
|
||||||
const recentEmojis = useRecentEmojis();
|
const recentEmojis = useRecentEmojis();
|
||||||
const skinTone = useSelector<StateType, number>(getEmojiSkinTone);
|
const skinTone = useSelector(getEmojiSkinTone);
|
||||||
const replyState = useSelector(getStoryReplies);
|
const replyState = useSelector(getStoryReplies);
|
||||||
const hasAllStoriesUnmuted = useSelector<StateType, boolean>(
|
const hasAllStoriesUnmuted = useSelector(getHasAllStoriesUnmuted);
|
||||||
getHasAllStoriesUnmuted
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasActiveCall = useSelector(isInFullScreenCall);
|
const hasActiveCall = useSelector(isInFullScreenCall);
|
||||||
const hasViewReceiptSetting = useSelector<StateType, boolean>(
|
const hasViewReceiptSetting = useSelector(getHasStoryViewReceiptSetting);
|
||||||
getHasStoryViewReceiptSetting
|
|
||||||
);
|
|
||||||
|
|
||||||
const isFormattingEnabled = useSelector(getTextFormattingEnabled);
|
const isFormattingEnabled = useSelector(getTextFormattingEnabled);
|
||||||
|
|
||||||
const { pauseVoiceNotePlayer } = useAudioPlayerActions();
|
const { pauseVoiceNotePlayer } = useAudioPlayerActions();
|
||||||
|
@ -161,4 +140,4 @@ export function SmartStoryViewer(): JSX.Element | null {
|
||||||
{...storiesActions}
|
{...storiesActions}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
|
@ -1,46 +1,47 @@
|
||||||
// Copyright 2019 Signal Messenger, LLC
|
// Copyright 2019 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { isEmpty, pick } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
import React from 'react';
|
import React, { memo, useCallback } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import type { ReadonlyDeep } from 'type-fest';
|
import type { ReadonlyDeep } from 'type-fest';
|
||||||
import { mapDispatchToProps } from '../actions';
|
|
||||||
import type { WarningType as TimelineWarningType } from '../../components/conversation/Timeline';
|
import type { WarningType as TimelineWarningType } from '../../components/conversation/Timeline';
|
||||||
import { Timeline } from '../../components/conversation/Timeline';
|
import { Timeline } from '../../components/conversation/Timeline';
|
||||||
import type { StateType } from '../reducer';
|
import { ContactSpoofingType } from '../../util/contactSpoofing';
|
||||||
import type { ConversationType } from '../ducks/conversations';
|
|
||||||
|
|
||||||
import { getIntl, getTheme } from '../selectors/user';
|
|
||||||
import {
|
|
||||||
getMessages,
|
|
||||||
getConversationByServiceIdSelector,
|
|
||||||
getConversationMessagesSelector,
|
|
||||||
getConversationSelector,
|
|
||||||
getInvitedContactsForNewlyCreatedGroup,
|
|
||||||
getSafeConversationWithSameTitle,
|
|
||||||
getTargetedMessage,
|
|
||||||
} from '../selectors/conversations';
|
|
||||||
import { selectAudioPlayerActive } from '../selectors/audioPlayer';
|
|
||||||
|
|
||||||
import { SmartTimelineItem, type SmartTimelineItemProps } from './TimelineItem';
|
|
||||||
import { SmartCollidingAvatars } from './CollidingAvatars';
|
|
||||||
import type { PropsType as SmartCollidingAvatarsPropsType } from './CollidingAvatars';
|
|
||||||
import { SmartContactSpoofingReviewDialog } from './ContactSpoofingReviewDialog';
|
|
||||||
import type { PropsType as SmartContactSpoofingReviewDialogPropsType } from './ContactSpoofingReviewDialog';
|
|
||||||
import { SmartTypingBubble } from './TypingBubble';
|
|
||||||
import { SmartHeroRow } from './HeroRow';
|
|
||||||
|
|
||||||
import { missingCaseError } from '../../util/missingCaseError';
|
|
||||||
import { getGroupMemberships } from '../../util/getGroupMemberships';
|
import { getGroupMemberships } from '../../util/getGroupMemberships';
|
||||||
import {
|
import {
|
||||||
dehydrateCollisionsWithConversations,
|
dehydrateCollisionsWithConversations,
|
||||||
getCollisionsFromMemberships,
|
getCollisionsFromMemberships,
|
||||||
} from '../../util/groupMemberNameCollisions';
|
} from '../../util/groupMemberNameCollisions';
|
||||||
import { ContactSpoofingType } from '../../util/contactSpoofing';
|
import { missingCaseError } from '../../util/missingCaseError';
|
||||||
|
import { useCallingActions } from '../ducks/calling';
|
||||||
|
import {
|
||||||
|
useConversationsActions,
|
||||||
|
type ConversationType,
|
||||||
|
} from '../ducks/conversations';
|
||||||
|
import type { StateType } from '../reducer';
|
||||||
|
import { selectAudioPlayerActive } from '../selectors/audioPlayer';
|
||||||
import { getPreferredBadgeSelector } from '../selectors/badges';
|
import { getPreferredBadgeSelector } from '../selectors/badges';
|
||||||
|
import {
|
||||||
|
getConversationByServiceIdSelector,
|
||||||
|
getConversationMessagesSelector,
|
||||||
|
getConversationSelector,
|
||||||
|
getHasContactSpoofingReview,
|
||||||
|
getInvitedContactsForNewlyCreatedGroup,
|
||||||
|
getMessages,
|
||||||
|
getSafeConversationWithSameTitle,
|
||||||
|
getSelectedConversationId,
|
||||||
|
getTargetedMessage,
|
||||||
|
} from '../selectors/conversations';
|
||||||
|
import { getIntl, getTheme } from '../selectors/user';
|
||||||
|
import type { PropsType as SmartCollidingAvatarsPropsType } from './CollidingAvatars';
|
||||||
|
import { SmartCollidingAvatars } from './CollidingAvatars';
|
||||||
|
import type { PropsType as SmartContactSpoofingReviewDialogPropsType } from './ContactSpoofingReviewDialog';
|
||||||
|
import { SmartContactSpoofingReviewDialog } from './ContactSpoofingReviewDialog';
|
||||||
|
import { SmartHeroRow } from './HeroRow';
|
||||||
import { SmartMiniPlayer } from './MiniPlayer';
|
import { SmartMiniPlayer } from './MiniPlayer';
|
||||||
|
import { SmartTimelineItem, type SmartTimelineItemProps } from './TimelineItem';
|
||||||
|
import { SmartTypingBubble } from './TypingBubble';
|
||||||
|
|
||||||
type ExternalProps = {
|
type ExternalProps = {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -144,60 +145,145 @@ const getWarning = (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state: StateType, props: ExternalProps) => {
|
export const SmartTimeline = memo(function SmartTimeline({
|
||||||
const { id } = props;
|
|
||||||
|
|
||||||
const conversation = getConversationSelector(state)(id);
|
|
||||||
|
|
||||||
const conversationMessages = getConversationMessagesSelector(state)(id);
|
|
||||||
const targetedMessage = getTargetedMessage(state);
|
|
||||||
|
|
||||||
const getTimestampForMessage = (messageId: string): undefined | number =>
|
|
||||||
getMessages(state)[messageId]?.timestamp;
|
|
||||||
|
|
||||||
const shouldShowMiniPlayer = Boolean(selectAudioPlayerActive(state));
|
|
||||||
|
|
||||||
return {
|
|
||||||
id,
|
id,
|
||||||
...pick(conversation, [
|
}: ExternalProps) {
|
||||||
'unreadCount',
|
const activeAudioPlayer = useSelector(selectAudioPlayerActive);
|
||||||
'unreadMentionsCount',
|
const conversationMessagesSelector = useSelector(
|
||||||
'isGroupV1AndDisabled',
|
getConversationMessagesSelector
|
||||||
'typingContactIdTimestamps',
|
);
|
||||||
]),
|
const conversationSelector = useSelector(getConversationSelector);
|
||||||
isBlocked: conversation.isBlocked ?? false,
|
const getPreferredBadge = useSelector(getPreferredBadgeSelector);
|
||||||
isConversationSelected: state.conversations.selectedConversationId === id,
|
const hasContactSpoofingReview = useSelector(getHasContactSpoofingReview);
|
||||||
isIncomingMessageRequest: Boolean(
|
const i18n = useSelector(getIntl);
|
||||||
!conversation.acceptedMessageRequest &&
|
const invitedContactsForNewlyCreatedGroup = useSelector(
|
||||||
conversation.removalStage !== 'justNotification'
|
getInvitedContactsForNewlyCreatedGroup
|
||||||
),
|
);
|
||||||
isSomeoneTyping: Boolean(
|
const messages = useSelector(getMessages);
|
||||||
Object.keys(conversation.typingContactIdTimestamps ?? {}).length > 0
|
const selectedConversationId = useSelector(getSelectedConversationId);
|
||||||
),
|
const targetedMessage = useSelector(getTargetedMessage);
|
||||||
...conversationMessages,
|
const theme = useSelector(getTheme);
|
||||||
|
|
||||||
invitedContactsForNewlyCreatedGroup:
|
const conversation = conversationSelector(id);
|
||||||
getInvitedContactsForNewlyCreatedGroup(state),
|
const conversationMessages = conversationMessagesSelector(id);
|
||||||
targetedMessageId: targetedMessage ? targetedMessage.id : undefined,
|
|
||||||
shouldShowMiniPlayer,
|
|
||||||
|
|
||||||
warning: getWarning(conversation, state),
|
const warning = useSelector(
|
||||||
hasContactSpoofingReview: state.conversations.hasContactSpoofingReview,
|
useCallback(
|
||||||
|
(state: StateType) => {
|
||||||
|
return getWarning(conversation, state);
|
||||||
|
},
|
||||||
|
[conversation]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
getTimestampForMessage,
|
const {
|
||||||
getPreferredBadge: getPreferredBadgeSelector(state),
|
acknowledgeGroupMemberNameCollisions,
|
||||||
i18n: getIntl(state),
|
clearInvitedServiceIdsForNewlyCreatedGroup,
|
||||||
theme: getTheme(state),
|
clearTargetedMessage,
|
||||||
|
closeContactSpoofingReview,
|
||||||
|
discardMessages,
|
||||||
|
loadNewerMessages,
|
||||||
|
loadNewestMessages,
|
||||||
|
loadOlderMessages,
|
||||||
|
markMessageRead,
|
||||||
|
reviewConversationNameCollision,
|
||||||
|
scrollToOldestUnreadMention,
|
||||||
|
setIsNearBottom,
|
||||||
|
targetMessage,
|
||||||
|
} = useConversationsActions();
|
||||||
|
const { peekGroupCallForTheFirstTime, peekGroupCallIfItHasMembers } =
|
||||||
|
useCallingActions();
|
||||||
|
|
||||||
renderCollidingAvatars,
|
const getTimestampForMessage = useCallback(
|
||||||
renderContactSpoofingReviewDialog,
|
(messageId: string): undefined | number => {
|
||||||
renderHeroRow,
|
return messages[messageId]?.timestamp;
|
||||||
renderItem,
|
},
|
||||||
renderMiniPlayer,
|
[messages]
|
||||||
renderTypingBubble,
|
);
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
const shouldShowMiniPlayer = activeAudioPlayer != null;
|
||||||
|
const {
|
||||||
|
acceptedMessageRequest,
|
||||||
|
isBlocked = false,
|
||||||
|
isGroupV1AndDisabled,
|
||||||
|
removalStage,
|
||||||
|
typingContactIdTimestamps = {},
|
||||||
|
unreadCount,
|
||||||
|
unreadMentionsCount,
|
||||||
|
} = conversation ?? {};
|
||||||
|
const {
|
||||||
|
haveNewest,
|
||||||
|
haveOldest,
|
||||||
|
isNearBottom,
|
||||||
|
items,
|
||||||
|
messageChangeCounter,
|
||||||
|
messageLoadingState,
|
||||||
|
oldestUnseenIndex,
|
||||||
|
scrollToIndex,
|
||||||
|
scrollToIndexCounter,
|
||||||
|
totalUnseen,
|
||||||
|
} = conversationMessages;
|
||||||
|
|
||||||
export const SmartTimeline = smart(Timeline);
|
const isConversationSelected = selectedConversationId === id;
|
||||||
|
const isIncomingMessageRequest =
|
||||||
|
!acceptedMessageRequest && removalStage !== 'justNotification';
|
||||||
|
const isSomeoneTyping = Object.keys(typingContactIdTimestamps).length > 0;
|
||||||
|
const targetedMessageId = targetedMessage?.id;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Timeline
|
||||||
|
acknowledgeGroupMemberNameCollisions={
|
||||||
|
acknowledgeGroupMemberNameCollisions
|
||||||
|
}
|
||||||
|
clearInvitedServiceIdsForNewlyCreatedGroup={
|
||||||
|
clearInvitedServiceIdsForNewlyCreatedGroup
|
||||||
|
}
|
||||||
|
clearTargetedMessage={clearTargetedMessage}
|
||||||
|
closeContactSpoofingReview={closeContactSpoofingReview}
|
||||||
|
discardMessages={discardMessages}
|
||||||
|
getPreferredBadge={getPreferredBadge}
|
||||||
|
getTimestampForMessage={getTimestampForMessage}
|
||||||
|
hasContactSpoofingReview={hasContactSpoofingReview}
|
||||||
|
haveNewest={haveNewest}
|
||||||
|
haveOldest={haveOldest}
|
||||||
|
i18n={i18n}
|
||||||
|
id={id}
|
||||||
|
invitedContactsForNewlyCreatedGroup={invitedContactsForNewlyCreatedGroup}
|
||||||
|
isBlocked={isBlocked}
|
||||||
|
isConversationSelected={isConversationSelected}
|
||||||
|
isGroupV1AndDisabled={isGroupV1AndDisabled}
|
||||||
|
isIncomingMessageRequest={isIncomingMessageRequest}
|
||||||
|
isNearBottom={isNearBottom}
|
||||||
|
isSomeoneTyping={isSomeoneTyping}
|
||||||
|
items={items}
|
||||||
|
loadNewerMessages={loadNewerMessages}
|
||||||
|
loadNewestMessages={loadNewestMessages}
|
||||||
|
loadOlderMessages={loadOlderMessages}
|
||||||
|
markMessageRead={markMessageRead}
|
||||||
|
messageChangeCounter={messageChangeCounter}
|
||||||
|
messageLoadingState={messageLoadingState}
|
||||||
|
oldestUnseenIndex={oldestUnseenIndex}
|
||||||
|
peekGroupCallForTheFirstTime={peekGroupCallForTheFirstTime}
|
||||||
|
peekGroupCallIfItHasMembers={peekGroupCallIfItHasMembers}
|
||||||
|
renderCollidingAvatars={renderCollidingAvatars}
|
||||||
|
renderContactSpoofingReviewDialog={renderContactSpoofingReviewDialog}
|
||||||
|
renderHeroRow={renderHeroRow}
|
||||||
|
renderItem={renderItem}
|
||||||
|
renderMiniPlayer={renderMiniPlayer}
|
||||||
|
renderTypingBubble={renderTypingBubble}
|
||||||
|
reviewConversationNameCollision={reviewConversationNameCollision}
|
||||||
|
scrollToIndex={scrollToIndex}
|
||||||
|
scrollToIndexCounter={scrollToIndexCounter}
|
||||||
|
scrollToOldestUnreadMention={scrollToOldestUnreadMention}
|
||||||
|
setIsNearBottom={setIsNearBottom}
|
||||||
|
shouldShowMiniPlayer={shouldShowMiniPlayer}
|
||||||
|
targetedMessageId={targetedMessageId}
|
||||||
|
targetMessage={targetMessage}
|
||||||
|
theme={theme}
|
||||||
|
totalUnseen={totalUnseen}
|
||||||
|
unreadCount={unreadCount}
|
||||||
|
unreadMentionsCount={unreadMentionsCount}
|
||||||
|
warning={warning}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import type { RefObject } from 'react';
|
import type { RefObject } from 'react';
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback, memo } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { TimelineItem } from '../../components/conversation/TimelineItem';
|
import { TimelineItem } from '../../components/conversation/TimelineItem';
|
||||||
|
@ -56,7 +56,9 @@ function renderContact(contactId: string): JSX.Element {
|
||||||
function renderUniversalTimerNotification(): JSX.Element {
|
function renderUniversalTimerNotification(): JSX.Element {
|
||||||
return <SmartUniversalTimerNotification />;
|
return <SmartUniversalTimerNotification />;
|
||||||
}
|
}
|
||||||
export function SmartTimelineItem(props: SmartTimelineItemProps): JSX.Element {
|
export const SmartTimelineItem = memo(function SmartTimelineItem(
|
||||||
|
props: SmartTimelineItemProps
|
||||||
|
): JSX.Element {
|
||||||
const {
|
const {
|
||||||
containerElementRef,
|
containerElementRef,
|
||||||
containerWidthBreakpoint,
|
containerWidthBreakpoint,
|
||||||
|
@ -224,4 +226,4 @@ export function SmartTimelineItem(props: SmartTimelineItemProps): JSX.Element {
|
||||||
toggleSelectMessage={toggleSelectMessage}
|
toggleSelectMessage={toggleSelectMessage}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue