Allow adding to a group by phone number
This commit is contained in:
parent
76a1a805ef
commit
9568d5792e
49 changed files with 1842 additions and 693 deletions
|
@ -21,15 +21,13 @@ import { getOwn } from '../../util/getOwn';
|
|||
import { assert, strictAssert } from '../../util/assert';
|
||||
import * as universalExpireTimer from '../../util/universalExpireTimer';
|
||||
import { trigger } from '../../shims/events';
|
||||
import type {
|
||||
ShowUsernameNotFoundModalActionType,
|
||||
ToggleProfileEditorErrorActionType,
|
||||
} from './globalModals';
|
||||
import {
|
||||
TOGGLE_PROFILE_EDITOR_ERROR,
|
||||
actions as globalModalActions,
|
||||
} from './globalModals';
|
||||
import type { ToggleProfileEditorErrorActionType } from './globalModals';
|
||||
import { TOGGLE_PROFILE_EDITOR_ERROR } from './globalModals';
|
||||
import { isRecord } from '../../util/isRecord';
|
||||
import type {
|
||||
UUIDFetchStateKeyType,
|
||||
UUIDFetchStateType,
|
||||
} from '../../util/uuidFetchState';
|
||||
|
||||
import type {
|
||||
AvatarColorType,
|
||||
|
@ -45,7 +43,6 @@ import type { BodyRangeType } from '../../types/Util';
|
|||
import { CallMode } from '../../types/Calling';
|
||||
import type { MediaItemType } from '../../types/MediaItem';
|
||||
import type { UUIDStringType } from '../../types/UUID';
|
||||
import { UUID } from '../../types/UUID';
|
||||
import {
|
||||
getGroupSizeRecommendedLimit,
|
||||
getGroupSizeHardLimit,
|
||||
|
@ -57,7 +54,6 @@ import { ContactSpoofingType } from '../../util/contactSpoofing';
|
|||
import { writeProfile } from '../../services/writeProfile';
|
||||
import { writeUsername } from '../../services/writeUsername';
|
||||
import {
|
||||
getConversationsByUsername,
|
||||
getConversationIdsStoppingSend,
|
||||
getConversationIdsStoppedForVerification,
|
||||
getMe,
|
||||
|
@ -76,8 +72,6 @@ import {
|
|||
} from './conversationsEnums';
|
||||
import { showToast } from '../../util/showToast';
|
||||
import { ToastFailedToDeleteUsername } from '../../components/ToastFailedToDeleteUsername';
|
||||
import { ToastFailedToFetchUsername } from '../../components/ToastFailedToFetchUsername';
|
||||
import { isValidUsername } from '../../types/Username';
|
||||
import { useBoundActions } from '../../hooks/useBoundActions';
|
||||
|
||||
import type { NoopActionType } from './noop';
|
||||
|
@ -288,20 +282,16 @@ export type ConversationVerificationData =
|
|||
canceledAt: number;
|
||||
};
|
||||
|
||||
export type FoundUsernameType = {
|
||||
uuid: UUIDStringType;
|
||||
username: string;
|
||||
};
|
||||
|
||||
type ComposerStateType =
|
||||
| {
|
||||
step: ComposerStep.StartDirectConversation;
|
||||
searchTerm: string;
|
||||
isFetchingUsername: boolean;
|
||||
uuidFetchState: UUIDFetchStateType;
|
||||
}
|
||||
| ({
|
||||
step: ComposerStep.ChooseGroupMembers;
|
||||
searchTerm: string;
|
||||
uuidFetchState: UUIDFetchStateType;
|
||||
} & ComposerGroupCreationState)
|
||||
| ({
|
||||
step: ComposerStep.SetGroupMetadata;
|
||||
|
@ -677,10 +667,11 @@ type SetComposeSearchTermActionType = {
|
|||
type: 'SET_COMPOSE_SEARCH_TERM';
|
||||
payload: { searchTerm: string };
|
||||
};
|
||||
type SetIsFetchingUsernameActionType = {
|
||||
type: 'SET_IS_FETCHING_USERNAME';
|
||||
type SetIsFetchingUUIDActionType = {
|
||||
type: 'SET_IS_FETCHING_UUID';
|
||||
payload: {
|
||||
isFetchingUsername: boolean;
|
||||
identifier: UUIDFetchStateKeyType;
|
||||
isFetching: boolean;
|
||||
};
|
||||
};
|
||||
type SetRecentMediaItemsActionType = {
|
||||
|
@ -773,7 +764,7 @@ export type ConversationActionType =
|
|||
| SetComposeGroupNameActionType
|
||||
| SetComposeSearchTermActionType
|
||||
| SetConversationHeaderTitleActionType
|
||||
| SetIsFetchingUsernameActionType
|
||||
| SetIsFetchingUUIDActionType
|
||||
| SetIsNearBottomActionType
|
||||
| SetMessageLoadingStateActionType
|
||||
| SetPreJoinConversationActionType
|
||||
|
@ -840,6 +831,7 @@ export const actions = {
|
|||
setComposeGroupExpireTimer,
|
||||
setComposeGroupName,
|
||||
setComposeSearchTerm,
|
||||
setIsFetchingUUID,
|
||||
setIsNearBottom,
|
||||
setMessageLoadingState,
|
||||
setPreJoinConversation,
|
||||
|
@ -849,9 +841,8 @@ export const actions = {
|
|||
showArchivedConversations,
|
||||
showChooseGroupMembers,
|
||||
showInbox,
|
||||
showConversation,
|
||||
startComposing,
|
||||
startNewConversationFromPhoneNumber,
|
||||
startNewConversationFromUsername,
|
||||
startSettingGroupMetadata,
|
||||
toggleAdmin,
|
||||
toggleConversationInChooseMembers,
|
||||
|
@ -1661,6 +1652,18 @@ function setIsNearBottom(
|
|||
},
|
||||
};
|
||||
}
|
||||
function setIsFetchingUUID(
|
||||
identifier: UUIDFetchStateKeyType,
|
||||
isFetching: boolean
|
||||
): SetIsFetchingUUIDActionType {
|
||||
return {
|
||||
type: 'SET_IS_FETCHING_UUID',
|
||||
payload: {
|
||||
identifier,
|
||||
isFetching,
|
||||
},
|
||||
};
|
||||
}
|
||||
function setSelectedConversationHeaderTitle(
|
||||
title?: string
|
||||
): SetConversationHeaderTitleActionType {
|
||||
|
@ -1772,117 +1775,6 @@ function showChooseGroupMembers(): ShowChooseGroupMembersActionType {
|
|||
return { type: 'SHOW_CHOOSE_GROUP_MEMBERS' };
|
||||
}
|
||||
|
||||
function startNewConversationFromPhoneNumber(
|
||||
e164: string
|
||||
): ThunkAction<void, RootStateType, unknown, ShowInboxActionType> {
|
||||
return dispatch => {
|
||||
trigger('showConversation', e164);
|
||||
|
||||
dispatch(showInbox());
|
||||
};
|
||||
}
|
||||
|
||||
async function checkForUsername(
|
||||
username: string
|
||||
): Promise<FoundUsernameType | undefined> {
|
||||
if (!isValidUsername(username)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
const profile = await window.textsecure.messaging.getProfileForUsername(
|
||||
username
|
||||
);
|
||||
|
||||
if (!profile.uuid) {
|
||||
log.error("checkForUsername: Returned profile didn't include a uuid");
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
uuid: UUID.cast(profile.uuid),
|
||||
username,
|
||||
};
|
||||
} catch (error: unknown) {
|
||||
if (!isRecord(error)) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (error.code === 404) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function startNewConversationFromUsername(
|
||||
username: string
|
||||
): ThunkAction<
|
||||
void,
|
||||
RootStateType,
|
||||
unknown,
|
||||
| ShowInboxActionType
|
||||
| SetIsFetchingUsernameActionType
|
||||
| ShowUsernameNotFoundModalActionType
|
||||
> {
|
||||
return async (dispatch, getState) => {
|
||||
const state = getState();
|
||||
|
||||
const byUsername = getConversationsByUsername(state);
|
||||
const knownConversation = getOwn(byUsername, username);
|
||||
if (knownConversation && knownConversation.uuid) {
|
||||
trigger('showConversation', knownConversation.uuid, username);
|
||||
dispatch(showInbox());
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: 'SET_IS_FETCHING_USERNAME',
|
||||
payload: {
|
||||
isFetchingUsername: true,
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
const foundUsername = await checkForUsername(username);
|
||||
dispatch({
|
||||
type: 'SET_IS_FETCHING_USERNAME',
|
||||
payload: {
|
||||
isFetchingUsername: false,
|
||||
},
|
||||
});
|
||||
|
||||
if (!foundUsername) {
|
||||
dispatch(globalModalActions.showUsernameNotFoundModal(username));
|
||||
return;
|
||||
}
|
||||
|
||||
trigger(
|
||||
'showConversation',
|
||||
foundUsername.uuid,
|
||||
undefined,
|
||||
foundUsername.username
|
||||
);
|
||||
dispatch(showInbox());
|
||||
} catch (error) {
|
||||
log.error(
|
||||
'startNewConversationFromUsername: Something went wrong fetching username:',
|
||||
error.stack
|
||||
);
|
||||
|
||||
dispatch({
|
||||
type: 'SET_IS_FETCHING_USERNAME',
|
||||
payload: {
|
||||
isFetchingUsername: false,
|
||||
},
|
||||
});
|
||||
|
||||
showToast(ToastFailedToFetchUsername);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function startSettingGroupMetadata(): StartSettingGroupMetadataActionType {
|
||||
return { type: 'START_SETTING_GROUP_METADATA' };
|
||||
}
|
||||
|
@ -2029,6 +1921,14 @@ function showInbox(): ShowInboxActionType {
|
|||
payload: null,
|
||||
};
|
||||
}
|
||||
function showConversation(
|
||||
conversationId: string
|
||||
): ThunkAction<void, RootStateType, unknown, ShowInboxActionType> {
|
||||
return dispatch => {
|
||||
trigger('showConversation', conversationId);
|
||||
dispatch(showInbox());
|
||||
};
|
||||
}
|
||||
function showArchivedConversations(): ShowArchivedConversationsActionType {
|
||||
return {
|
||||
type: 'SHOW_ARCHIVED_CONVERSATIONS',
|
||||
|
@ -3060,7 +2960,7 @@ export function reducer(
|
|||
composer: {
|
||||
step: ComposerStep.StartDirectConversation,
|
||||
searchTerm: '',
|
||||
isFetchingUsername: false,
|
||||
uuidFetchState: {},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -3103,6 +3003,7 @@ export function reducer(
|
|||
composer: {
|
||||
step: ComposerStep.ChooseGroupMembers,
|
||||
searchTerm: '',
|
||||
uuidFetchState: {},
|
||||
selectedConversationIds,
|
||||
recommendedGroupSizeModalState,
|
||||
maximumGroupSizeModalState,
|
||||
|
@ -3235,26 +3136,36 @@ export function reducer(
|
|||
};
|
||||
}
|
||||
|
||||
if (action.type === 'SET_IS_FETCHING_USERNAME') {
|
||||
if (action.type === 'SET_IS_FETCHING_UUID') {
|
||||
const { composer } = state;
|
||||
if (!composer) {
|
||||
assert(
|
||||
false,
|
||||
'Setting compose username with the composer closed is a no-op'
|
||||
'Setting compose uuid fetch state with the composer closed is a no-op'
|
||||
);
|
||||
return state;
|
||||
}
|
||||
if (composer.step !== ComposerStep.StartDirectConversation) {
|
||||
assert(false, 'Setting compose username at this step is a no-op');
|
||||
if (
|
||||
composer.step !== ComposerStep.StartDirectConversation &&
|
||||
composer.step !== ComposerStep.ChooseGroupMembers
|
||||
) {
|
||||
assert(false, 'Setting compose uuid fetch state at this step is a no-op');
|
||||
return state;
|
||||
}
|
||||
const { isFetchingUsername } = action.payload;
|
||||
const { identifier, isFetching } = action.payload;
|
||||
|
||||
const { uuidFetchState } = composer;
|
||||
|
||||
return {
|
||||
...state,
|
||||
composer: {
|
||||
...composer,
|
||||
isFetchingUsername,
|
||||
uuidFetchState: isFetching
|
||||
? {
|
||||
...composer.uuidFetchState,
|
||||
[identifier]: isFetching,
|
||||
}
|
||||
: omit(uuidFetchState, identifier),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ export type GlobalModalsStateType = {
|
|||
readonly isWhatsNewVisible: boolean;
|
||||
readonly profileEditorHasError: boolean;
|
||||
readonly safetyNumberModalContactId?: string;
|
||||
readonly usernameNotFoundModalState?: UsernameNotFoundModalStateType;
|
||||
readonly userNotFoundModalState?: UserNotFoundModalStateType;
|
||||
};
|
||||
|
||||
// Actions
|
||||
|
@ -17,10 +17,8 @@ export type GlobalModalsStateType = {
|
|||
const HIDE_CONTACT_MODAL = 'globalModals/HIDE_CONTACT_MODAL';
|
||||
const SHOW_CONTACT_MODAL = 'globalModals/SHOW_CONTACT_MODAL';
|
||||
const SHOW_WHATS_NEW_MODAL = 'globalModals/SHOW_WHATS_NEW_MODAL_MODAL';
|
||||
const SHOW_USERNAME_NOT_FOUND_MODAL =
|
||||
'globalModals/SHOW_USERNAME_NOT_FOUND_MODAL';
|
||||
const HIDE_USERNAME_NOT_FOUND_MODAL =
|
||||
'globalModals/HIDE_USERNAME_NOT_FOUND_MODAL';
|
||||
const SHOW_UUID_NOT_FOUND_MODAL = 'globalModals/SHOW_UUID_NOT_FOUND_MODAL';
|
||||
const HIDE_UUID_NOT_FOUND_MODAL = 'globalModals/HIDE_UUID_NOT_FOUND_MODAL';
|
||||
const HIDE_WHATS_NEW_MODAL = 'globalModals/HIDE_WHATS_NEW_MODAL_MODAL';
|
||||
const TOGGLE_PROFILE_EDITOR = 'globalModals/TOGGLE_PROFILE_EDITOR';
|
||||
export const TOGGLE_PROFILE_EDITOR_ERROR =
|
||||
|
@ -32,9 +30,15 @@ export type ContactModalStateType = {
|
|||
conversationId?: string;
|
||||
};
|
||||
|
||||
export type UsernameNotFoundModalStateType = {
|
||||
username: string;
|
||||
};
|
||||
export type UserNotFoundModalStateType =
|
||||
| {
|
||||
type: 'phoneNumber';
|
||||
phoneNumber: string;
|
||||
}
|
||||
| {
|
||||
type: 'username';
|
||||
username: string;
|
||||
};
|
||||
|
||||
type HideContactModalActionType = {
|
||||
type: typeof HIDE_CONTACT_MODAL;
|
||||
|
@ -53,15 +57,13 @@ type ShowWhatsNewModalActionType = {
|
|||
type: typeof SHOW_WHATS_NEW_MODAL;
|
||||
};
|
||||
|
||||
type HideUsernameNotFoundModalActionType = {
|
||||
type: typeof HIDE_USERNAME_NOT_FOUND_MODAL;
|
||||
type HideUserNotFoundModalActionType = {
|
||||
type: typeof HIDE_UUID_NOT_FOUND_MODAL;
|
||||
};
|
||||
|
||||
export type ShowUsernameNotFoundModalActionType = {
|
||||
type: typeof SHOW_USERNAME_NOT_FOUND_MODAL;
|
||||
payload: {
|
||||
username: string;
|
||||
};
|
||||
export type ShowUserNotFoundModalActionType = {
|
||||
type: typeof SHOW_UUID_NOT_FOUND_MODAL;
|
||||
payload: UserNotFoundModalStateType;
|
||||
};
|
||||
|
||||
type ToggleProfileEditorActionType = {
|
||||
|
@ -82,8 +84,8 @@ export type GlobalModalsActionType =
|
|||
| ShowContactModalActionType
|
||||
| HideWhatsNewModalActionType
|
||||
| ShowWhatsNewModalActionType
|
||||
| HideUsernameNotFoundModalActionType
|
||||
| ShowUsernameNotFoundModalActionType
|
||||
| HideUserNotFoundModalActionType
|
||||
| ShowUserNotFoundModalActionType
|
||||
| ToggleProfileEditorActionType
|
||||
| ToggleProfileEditorErrorActionType
|
||||
| ToggleSafetyNumberModalActionType;
|
||||
|
@ -95,8 +97,8 @@ export const actions = {
|
|||
showContactModal,
|
||||
hideWhatsNewModal,
|
||||
showWhatsNewModal,
|
||||
hideUsernameNotFoundModal,
|
||||
showUsernameNotFoundModal,
|
||||
hideUserNotFoundModal,
|
||||
showUserNotFoundModal,
|
||||
toggleProfileEditor,
|
||||
toggleProfileEditorHasError,
|
||||
toggleSafetyNumberModal,
|
||||
|
@ -133,20 +135,18 @@ function showWhatsNewModal(): ShowWhatsNewModalActionType {
|
|||
};
|
||||
}
|
||||
|
||||
function hideUsernameNotFoundModal(): HideUsernameNotFoundModalActionType {
|
||||
function hideUserNotFoundModal(): HideUserNotFoundModalActionType {
|
||||
return {
|
||||
type: HIDE_USERNAME_NOT_FOUND_MODAL,
|
||||
type: HIDE_UUID_NOT_FOUND_MODAL,
|
||||
};
|
||||
}
|
||||
|
||||
function showUsernameNotFoundModal(
|
||||
username: string
|
||||
): ShowUsernameNotFoundModalActionType {
|
||||
function showUserNotFoundModal(
|
||||
payload: UserNotFoundModalStateType
|
||||
): ShowUserNotFoundModalActionType {
|
||||
return {
|
||||
type: SHOW_USERNAME_NOT_FOUND_MODAL,
|
||||
payload: {
|
||||
username,
|
||||
},
|
||||
type: SHOW_UUID_NOT_FOUND_MODAL,
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -209,20 +209,18 @@ export function reducer(
|
|||
};
|
||||
}
|
||||
|
||||
if (action.type === HIDE_USERNAME_NOT_FOUND_MODAL) {
|
||||
if (action.type === HIDE_UUID_NOT_FOUND_MODAL) {
|
||||
return {
|
||||
...state,
|
||||
usernameNotFoundModalState: undefined,
|
||||
userNotFoundModalState: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
if (action.type === SHOW_USERNAME_NOT_FOUND_MODAL) {
|
||||
const { username } = action.payload;
|
||||
|
||||
if (action.type === SHOW_UUID_NOT_FOUND_MODAL) {
|
||||
return {
|
||||
...state,
|
||||
usernameNotFoundModalState: {
|
||||
username,
|
||||
userNotFoundModalState: {
|
||||
...action.payload,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import {
|
|||
} from '../ducks/conversationsEnums';
|
||||
import { getOwn } from '../../util/getOwn';
|
||||
import { isNotNil } from '../../util/isNotNil';
|
||||
import type { UUIDFetchStateType } from '../../util/uuidFetchState';
|
||||
import { deconstructLookup } from '../../util/deconstructLookup';
|
||||
import type { PropsDataType as TimelinePropsType } from '../../components/conversation/Timeline';
|
||||
import type { TimelineItemType } from '../../components/conversation/TimelineItem';
|
||||
|
@ -394,21 +395,25 @@ export const getComposerConversationSearchTerm = createSelector(
|
|||
}
|
||||
);
|
||||
|
||||
export const getIsFetchingUsername = createSelector(
|
||||
export const getComposerUUIDFetchState = createSelector(
|
||||
getComposerState,
|
||||
(composer): boolean => {
|
||||
(composer): UUIDFetchStateType => {
|
||||
if (!composer) {
|
||||
assert(false, 'getIsFetchingUsername: composer is not open');
|
||||
return false;
|
||||
return {};
|
||||
}
|
||||
if (composer.step !== ComposerStep.StartDirectConversation) {
|
||||
if (
|
||||
composer.step !== ComposerStep.StartDirectConversation &&
|
||||
composer.step !== ComposerStep.ChooseGroupMembers
|
||||
) {
|
||||
assert(
|
||||
false,
|
||||
`getIsFetchingUsername: step ${composer.step} has no isFetchingUsername key`
|
||||
`getComposerUUIDFetchState: step ${composer.step} ` +
|
||||
'has no uuidFetchState key'
|
||||
);
|
||||
return false;
|
||||
return {};
|
||||
}
|
||||
return composer.isFetchingUsername;
|
||||
return composer.uuidFetchState;
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -512,28 +517,33 @@ const getNormalizedComposerConversationSearchTerm = createSelector(
|
|||
export const getFilteredComposeContacts = createSelector(
|
||||
getNormalizedComposerConversationSearchTerm,
|
||||
getComposableContacts,
|
||||
getRegionCode,
|
||||
(
|
||||
searchTerm: string,
|
||||
contacts: Array<ConversationType>
|
||||
contacts: Array<ConversationType>,
|
||||
regionCode: string | undefined
|
||||
): Array<ConversationType> => {
|
||||
return filterAndSortConversationsByTitle(contacts, searchTerm);
|
||||
return filterAndSortConversationsByTitle(contacts, searchTerm, regionCode);
|
||||
}
|
||||
);
|
||||
|
||||
export const getFilteredComposeGroups = createSelector(
|
||||
getNormalizedComposerConversationSearchTerm,
|
||||
getComposableGroups,
|
||||
getRegionCode,
|
||||
(
|
||||
searchTerm: string,
|
||||
groups: Array<ConversationType>
|
||||
groups: Array<ConversationType>,
|
||||
regionCode: string | undefined
|
||||
): Array<ConversationType> => {
|
||||
return filterAndSortConversationsByTitle(groups, searchTerm);
|
||||
return filterAndSortConversationsByTitle(groups, searchTerm, regionCode);
|
||||
}
|
||||
);
|
||||
|
||||
export const getFilteredCandidateContactsForNewGroup = createSelector(
|
||||
getCandidateContactsForNewGroup,
|
||||
getNormalizedComposerConversationSearchTerm,
|
||||
getRegionCode,
|
||||
filterAndSortConversationsByTitle
|
||||
);
|
||||
|
||||
|
|
63
ts/state/smart/ChooseGroupMembersModal.tsx
Normal file
63
ts/state/smart/ChooseGroupMembersModal.tsx
Normal file
|
@ -0,0 +1,63 @@
|
|||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import type { StateType } from '../reducer';
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
import { strictAssert } from '../../util/assert';
|
||||
import { lookupConversationWithoutUuid } from '../../util/lookupConversationWithoutUuid';
|
||||
|
||||
import type { StatePropsType } from '../../components/conversation/conversation-details/AddGroupMembersModal/ChooseGroupMembersModal';
|
||||
import { ChooseGroupMembersModal } from '../../components/conversation/conversation-details/AddGroupMembersModal/ChooseGroupMembersModal';
|
||||
|
||||
import { getIntl, getTheme, getRegionCode } from '../selectors/user';
|
||||
import {
|
||||
getCandidateContactsForNewGroup,
|
||||
getConversationByIdSelector,
|
||||
} from '../selectors/conversations';
|
||||
import { getPreferredBadgeSelector } from '../selectors/badges';
|
||||
|
||||
export type SmartChooseGroupMembersModalPropsType = {
|
||||
conversationIdsAlreadyInGroup: Set<string>;
|
||||
maxGroupSize: number;
|
||||
confirmAdds: () => void;
|
||||
onClose: () => void;
|
||||
removeSelectedContact: (_: string) => void;
|
||||
searchTerm: string;
|
||||
selectedConversationIds: ReadonlyArray<string>;
|
||||
setSearchTerm: (_: string) => void;
|
||||
toggleSelectedContact: (conversationId: string) => void;
|
||||
};
|
||||
|
||||
const mapStateToProps = (
|
||||
state: StateType,
|
||||
props: SmartChooseGroupMembersModalPropsType
|
||||
): StatePropsType => {
|
||||
const conversationSelector = getConversationByIdSelector(state);
|
||||
|
||||
const candidateContacts = getCandidateContactsForNewGroup(state);
|
||||
const selectedContacts = props.selectedConversationIds.map(conversationId => {
|
||||
const convo = conversationSelector(conversationId);
|
||||
strictAssert(
|
||||
convo,
|
||||
'<SmartChooseGroupMemberModal> selected conversation not found'
|
||||
);
|
||||
return convo;
|
||||
});
|
||||
|
||||
return {
|
||||
...props,
|
||||
regionCode: getRegionCode(state),
|
||||
candidateContacts,
|
||||
getPreferredBadge: getPreferredBadgeSelector(state),
|
||||
i18n: getIntl(state),
|
||||
theme: getTheme(state),
|
||||
selectedContacts,
|
||||
lookupConversationWithoutUuid,
|
||||
};
|
||||
};
|
||||
|
||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export const SmartChooseGroupMembersModal = smart(ChooseGroupMembersModal);
|
49
ts/state/smart/ConfirmAdditionsModal.tsx
Normal file
49
ts/state/smart/ConfirmAdditionsModal.tsx
Normal file
|
@ -0,0 +1,49 @@
|
|||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import type { StateType } from '../reducer';
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
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 type { RequestState } from '../../components/conversation/conversation-details/util';
|
||||
|
||||
import { getIntl } from '../selectors/user';
|
||||
import { getConversationByIdSelector } from '../selectors/conversations';
|
||||
|
||||
export type SmartConfirmAdditionsModalPropsType = {
|
||||
selectedConversationIds: ReadonlyArray<string>;
|
||||
groupTitle: string;
|
||||
makeRequest: () => void;
|
||||
onClose: () => void;
|
||||
requestState: RequestState;
|
||||
};
|
||||
|
||||
const mapStateToProps = (
|
||||
state: StateType,
|
||||
props: SmartConfirmAdditionsModalPropsType
|
||||
): StatePropsType => {
|
||||
const conversationSelector = getConversationByIdSelector(state);
|
||||
|
||||
const selectedContacts = props.selectedConversationIds.map(conversationId => {
|
||||
const convo = conversationSelector(conversationId);
|
||||
strictAssert(
|
||||
convo,
|
||||
'<SmartChooseGroupMemberModal> selected conversation not found'
|
||||
);
|
||||
return convo;
|
||||
});
|
||||
|
||||
return {
|
||||
...props,
|
||||
selectedContacts,
|
||||
i18n: getIntl(state),
|
||||
};
|
||||
};
|
||||
|
||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export const SmartConfirmAdditionsModal = smart(ConfirmAdditionsModal);
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright 2021-2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import type { StateType } from '../reducer';
|
||||
|
@ -8,7 +9,6 @@ import { mapDispatchToProps } from '../actions';
|
|||
import type { StateProps } from '../../components/conversation/conversation-details/ConversationDetails';
|
||||
import { ConversationDetails } from '../../components/conversation/conversation-details/ConversationDetails';
|
||||
import {
|
||||
getCandidateContactsForNewGroup,
|
||||
getConversationByIdSelector,
|
||||
getConversationByUuidSelector,
|
||||
} from '../selectors/conversations';
|
||||
|
@ -24,6 +24,10 @@ import {
|
|||
import { assert } from '../../util/assert';
|
||||
import { SignalService as Proto } from '../../protobuf';
|
||||
import { getConversationColorAttributes } from '../../util/getConversationColorAttributes';
|
||||
import type { SmartChooseGroupMembersModalPropsType } from './ChooseGroupMembersModal';
|
||||
import { SmartChooseGroupMembersModal } from './ChooseGroupMembersModal';
|
||||
import type { SmartConfirmAdditionsModalPropsType } from './ConfirmAdditionsModal';
|
||||
import { SmartConfirmAdditionsModal } from './ConfirmAdditionsModal';
|
||||
|
||||
export type SmartConversationDetailsProps = {
|
||||
addMembers: (conversationIds: ReadonlyArray<string>) => Promise<void>;
|
||||
|
@ -56,6 +60,18 @@ export type SmartConversationDetailsProps = {
|
|||
|
||||
const ACCESS_ENUM = Proto.AccessControl.AccessRequired;
|
||||
|
||||
const renderChooseGroupMembersModal = (
|
||||
props: SmartChooseGroupMembersModalPropsType
|
||||
) => {
|
||||
return <SmartChooseGroupMembersModal {...props} />;
|
||||
};
|
||||
|
||||
const renderConfirmAdditionsModal = (
|
||||
props: SmartConfirmAdditionsModalPropsType
|
||||
) => {
|
||||
return <SmartConfirmAdditionsModal {...props} />;
|
||||
};
|
||||
|
||||
const mapStateToProps = (
|
||||
state: StateType,
|
||||
props: SmartConversationDetailsProps
|
||||
|
@ -69,7 +85,6 @@ const mapStateToProps = (
|
|||
|
||||
const canEditGroupInfo = Boolean(conversation.canEditGroupInfo);
|
||||
const isAdmin = Boolean(conversation.areWeAdmin);
|
||||
const candidateContactsToAdd = getCandidateContactsForNewGroup(state);
|
||||
|
||||
const hasGroupLink =
|
||||
Boolean(conversation.groupLink) &&
|
||||
|
@ -88,7 +103,6 @@ const mapStateToProps = (
|
|||
areWeASubscriber: getAreWeASubscriber(state),
|
||||
badges,
|
||||
canEditGroupInfo,
|
||||
candidateContactsToAdd,
|
||||
conversation: {
|
||||
...conversation,
|
||||
...getConversationColorAttributes(conversation),
|
||||
|
@ -102,6 +116,8 @@ const mapStateToProps = (
|
|||
hasGroupLink,
|
||||
isGroup: conversation.type === 'group',
|
||||
theme: getTheme(state),
|
||||
renderChooseGroupMembersModal,
|
||||
renderConfirmAdditionsModal,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import type { LinkPreviewType } from '../../types/message/LinkPreviews';
|
|||
import { getPreferredBadgeSelector } from '../selectors/badges';
|
||||
import { getAllComposableConversations } from '../selectors/conversations';
|
||||
import { getLinkPreview } from '../selectors/linkPreviews';
|
||||
import { getIntl, getTheme } from '../selectors/user';
|
||||
import { getIntl, getTheme, getRegionCode } from '../selectors/user';
|
||||
import { getEmojiSkinTone } from '../selectors/items';
|
||||
import { selectRecentEmojis } from '../selectors/emojis';
|
||||
import type { AttachmentType } from '../../types/Attachment';
|
||||
|
@ -69,6 +69,7 @@ const mapStateToProps = (
|
|||
skinTone,
|
||||
onTextTooLong,
|
||||
theme: getTheme(state),
|
||||
regionCode: getRegionCode(state),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import type { PropsType as LeftPanePropsType } from '../../components/LeftPane';
|
|||
import { LeftPane, LeftPaneMode } from '../../components/LeftPane';
|
||||
import type { StateType } from '../reducer';
|
||||
import { missingCaseError } from '../../util/missingCaseError';
|
||||
import { lookupConversationWithoutUuid } from '../../util/lookupConversationWithoutUuid';
|
||||
|
||||
import { ComposerStep, OneTimeModalState } from '../ducks/conversationsEnums';
|
||||
import {
|
||||
|
@ -32,11 +33,11 @@ import {
|
|||
getComposeGroupName,
|
||||
getComposerConversationSearchTerm,
|
||||
getComposerStep,
|
||||
getComposerUUIDFetchState,
|
||||
getComposeSelectedContacts,
|
||||
getFilteredCandidateContactsForNewGroup,
|
||||
getFilteredComposeContacts,
|
||||
getFilteredComposeGroups,
|
||||
getIsFetchingUsername,
|
||||
getLeftPaneLists,
|
||||
getMaximumGroupSizeModalState,
|
||||
getRecommendedGroupSizeModalState,
|
||||
|
@ -141,7 +142,7 @@ const getModeSpecificProps = (
|
|||
regionCode: getRegionCode(state),
|
||||
searchTerm: getComposerConversationSearchTerm(state),
|
||||
isUsernamesEnabled: getUsernamesEnabled(state),
|
||||
isFetchingUsername: getIsFetchingUsername(state),
|
||||
uuidFetchState: getComposerUUIDFetchState(state),
|
||||
};
|
||||
case ComposerStep.ChooseGroupMembers:
|
||||
return {
|
||||
|
@ -152,8 +153,10 @@ const getModeSpecificProps = (
|
|||
OneTimeModalState.Showing,
|
||||
isShowingMaximumGroupSizeModal:
|
||||
getMaximumGroupSizeModalState(state) === OneTimeModalState.Showing,
|
||||
regionCode: getRegionCode(state),
|
||||
searchTerm: getComposerConversationSearchTerm(state),
|
||||
selectedContacts: getComposeSelectedContacts(state),
|
||||
uuidFetchState: getComposerUUIDFetchState(state),
|
||||
};
|
||||
case ComposerStep.SetGroupMetadata:
|
||||
return {
|
||||
|
@ -192,6 +195,7 @@ const mapStateToProps = (state: StateType) => {
|
|||
renderUpdateDialog,
|
||||
renderCaptchaDialog,
|
||||
renderCrashReportDialog,
|
||||
lookupConversationWithoutUuid,
|
||||
theme: getTheme(state),
|
||||
};
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue