Add to group by username

This commit is contained in:
Fedor Indutny 2022-06-16 17:38:28 -07:00 committed by GitHub
parent 8dd321d0b6
commit 973b2264fe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 332 additions and 45 deletions

View file

@ -25,6 +25,7 @@ import { ContactListItem } from './conversationList/ContactListItem';
import type { ContactCheckboxDisabledReason } from './conversationList/ContactCheckbox'; import type { ContactCheckboxDisabledReason } from './conversationList/ContactCheckbox';
import { ContactCheckbox as ContactCheckboxComponent } from './conversationList/ContactCheckbox'; import { ContactCheckbox as ContactCheckboxComponent } from './conversationList/ContactCheckbox';
import { PhoneNumberCheckbox as PhoneNumberCheckboxComponent } from './conversationList/PhoneNumberCheckbox'; import { PhoneNumberCheckbox as PhoneNumberCheckboxComponent } from './conversationList/PhoneNumberCheckbox';
import { UsernameCheckbox as UsernameCheckboxComponent } from './conversationList/UsernameCheckbox';
import { CreateNewGroupButton } from './conversationList/CreateNewGroupButton'; import { CreateNewGroupButton } from './conversationList/CreateNewGroupButton';
import { StartNewConversation as StartNewConversationComponent } from './conversationList/StartNewConversation'; import { StartNewConversation as StartNewConversationComponent } from './conversationList/StartNewConversation';
import { SearchResultsLoadingFakeHeader as SearchResultsLoadingFakeHeaderComponent } from './conversationList/SearchResultsLoadingFakeHeader'; import { SearchResultsLoadingFakeHeader as SearchResultsLoadingFakeHeaderComponent } from './conversationList/SearchResultsLoadingFakeHeader';
@ -32,19 +33,20 @@ import { SearchResultsLoadingFakeRow as SearchResultsLoadingFakeRowComponent } f
import { UsernameSearchResultListItem } from './conversationList/UsernameSearchResultListItem'; import { UsernameSearchResultListItem } from './conversationList/UsernameSearchResultListItem';
export enum RowType { export enum RowType {
ArchiveButton, ArchiveButton = 'ArchiveButton',
Blank, Blank = 'Blank',
Contact, Contact = 'Contact',
ContactCheckbox, ContactCheckbox = 'ContactCheckbox',
PhoneNumberCheckbox, PhoneNumberCheckbox = 'PhoneNumberCheckbox',
Conversation, UsernameCheckbox = 'UsernameCheckbox',
CreateNewGroup, Conversation = 'Conversation',
Header, CreateNewGroup = 'CreateNewGroup',
MessageSearchResult, Header = 'Header',
SearchResultsLoadingFakeHeader, MessageSearchResult = 'MessageSearchResult',
SearchResultsLoadingFakeRow, SearchResultsLoadingFakeHeader = 'SearchResultsLoadingFakeHeader',
StartNewConversation, SearchResultsLoadingFakeRow = 'SearchResultsLoadingFakeRow',
UsernameSearchResult, StartNewConversation = 'StartNewConversation',
UsernameSearchResult = 'UsernameSearchResult',
} }
type ArchiveButtonRowType = { type ArchiveButtonRowType = {
@ -74,6 +76,13 @@ type PhoneNumberCheckboxRowType = {
isFetching: boolean; isFetching: boolean;
}; };
type UsernameCheckboxRowType = {
type: RowType.UsernameCheckbox;
username: string;
isChecked: boolean;
isFetching: boolean;
};
type ConversationRowType = { type ConversationRowType = {
type: RowType.Conversation; type: RowType.Conversation;
conversation: ConversationListItemPropsType; conversation: ConversationListItemPropsType;
@ -119,6 +128,7 @@ export type Row =
| ContactRowType | ContactRowType
| ContactCheckboxRowType | ContactCheckboxRowType
| PhoneNumberCheckboxRowType | PhoneNumberCheckboxRowType
| UsernameCheckboxRowType
| ConversationRowType | ConversationRowType
| CreateNewGroupRowType | CreateNewGroupRowType
| MessageRowType | MessageRowType
@ -283,6 +293,23 @@ export const ConversationList: React.FC<PropsType> = ({
/> />
); );
break; break;
case RowType.UsernameCheckbox:
result = (
<UsernameCheckboxComponent
username={row.username}
lookupConversationWithoutUuid={lookupConversationWithoutUuid}
showUserNotFoundModal={showUserNotFoundModal}
setIsFetchingUUID={setIsFetchingUUID}
toggleConversationInChooseMembers={conversationId =>
onClickContactCheckbox(conversationId, undefined)
}
isChecked={row.isChecked}
isFetching={row.isFetching}
i18n={i18n}
theme={theme}
/>
);
break;
case RowType.Conversation: { case RowType.Conversation: {
const itemProps = pick(row.conversation, [ const itemProps = pick(row.conversation, [
'acceptedMessageRequest', 'acceptedMessageRequest',

View file

@ -883,6 +883,7 @@ export const ChooseGroupMembersPartialPhoneNumber = (): JSX.Element => (
candidateContacts: [], candidateContacts: [],
isShowingRecommendedGroupSizeModal: false, isShowingRecommendedGroupSizeModal: false,
isShowingMaximumGroupSizeModal: false, isShowingMaximumGroupSizeModal: false,
isUsernamesEnabled: true,
searchTerm: '+1(212) 555', searchTerm: '+1(212) 555',
regionCode: 'US', regionCode: 'US',
selectedContacts: [], selectedContacts: [],
@ -904,6 +905,7 @@ export const ChooseGroupMembersValidPhoneNumber = (): JSX.Element => (
candidateContacts: [], candidateContacts: [],
isShowingRecommendedGroupSizeModal: false, isShowingRecommendedGroupSizeModal: false,
isShowingMaximumGroupSizeModal: false, isShowingMaximumGroupSizeModal: false,
isUsernamesEnabled: true,
searchTerm: '+1(212) 555 5454', searchTerm: '+1(212) 555 5454',
regionCode: 'US', regionCode: 'US',
selectedContacts: [], selectedContacts: [],
@ -916,6 +918,28 @@ ChooseGroupMembersValidPhoneNumber.story = {
name: 'Choose Group Members: Valid phone number', name: 'Choose Group Members: Valid phone number',
}; };
export const ChooseGroupMembersUsername = (): JSX.Element => (
<LeftPane
{...useProps({
modeSpecificProps: {
mode: LeftPaneMode.ChooseGroupMembers,
uuidFetchState: {},
candidateContacts: [],
isShowingRecommendedGroupSizeModal: false,
isShowingMaximumGroupSizeModal: false,
isUsernamesEnabled: true,
searchTerm: '@signal',
regionCode: 'US',
selectedContacts: [],
},
})}
/>
);
ChooseGroupMembersUsername.story = {
name: 'Choose Group Members: username',
};
export const GroupMetadataNoTimer = (): JSX.Element => ( export const GroupMetadataNoTimer = (): JSX.Element => (
<LeftPane <LeftPane
{...useProps({ {...useProps({

View file

@ -68,6 +68,7 @@ const createProps = (
i18n={i18n} i18n={i18n}
lookupConversationWithoutUuid={lookupConversationWithoutUuid} lookupConversationWithoutUuid={lookupConversationWithoutUuid}
showUserNotFoundModal={action('showUserNotFoundModal')} showUserNotFoundModal={action('showUserNotFoundModal')}
isUsernamesEnabled
/> />
); );
}, },

View file

@ -14,6 +14,7 @@ import type { MeasuredComponentProps } from 'react-measure';
import Measure from 'react-measure'; import Measure from 'react-measure';
import type { LocalizerType, ThemeType } from '../../../../types/Util'; import type { LocalizerType, ThemeType } from '../../../../types/Util';
import { getUsernameFromSearch } from '../../../../types/Username';
import { assert } from '../../../../util/assert'; import { assert } from '../../../../util/assert';
import { refMerger } from '../../../../util/refMerger'; import { refMerger } from '../../../../util/refMerger';
import { useRestoreFocus } from '../../../../hooks/useRestoreFocus'; import { useRestoreFocus } from '../../../../hooks/useRestoreFocus';
@ -27,7 +28,10 @@ import type {
UUIDFetchStateKeyType, UUIDFetchStateKeyType,
UUIDFetchStateType, UUIDFetchStateType,
} from '../../../../util/uuidFetchState'; } from '../../../../util/uuidFetchState';
import { isFetchingByE164 } from '../../../../util/uuidFetchState'; import {
isFetchingByE164,
isFetchingByUsername,
} from '../../../../util/uuidFetchState';
import { ModalHost } from '../../../ModalHost'; import { ModalHost } from '../../../ModalHost';
import { ContactPills } from '../../../ContactPills'; import { ContactPills } from '../../../ContactPills';
import { ContactPill } from '../../../ContactPill'; import { ContactPill } from '../../../ContactPill';
@ -53,6 +57,7 @@ export type StatePropsType = {
removeSelectedContact: (_: string) => void; removeSelectedContact: (_: string) => void;
setSearchTerm: (_: string) => void; setSearchTerm: (_: string) => void;
toggleSelectedContact: (conversationId: string) => void; toggleSelectedContact: (conversationId: string) => void;
isUsernamesEnabled: boolean;
} & Pick< } & Pick<
LookupConversationWithoutUuidActionsType, LookupConversationWithoutUuidActionsType,
'lookupConversationWithoutUuid' 'lookupConversationWithoutUuid'
@ -83,6 +88,7 @@ export const ChooseGroupMembersModal: FunctionComponent<PropsType> = ({
toggleSelectedContact, toggleSelectedContact,
lookupConversationWithoutUuid, lookupConversationWithoutUuid,
showUserNotFoundModal, showUserNotFoundModal,
isUsernamesEnabled,
}) => { }) => {
const [focusRef] = useRestoreFocus(); const [focusRef] = useRestoreFocus();
@ -99,6 +105,21 @@ export const ChooseGroupMembersModal: FunctionComponent<PropsType> = ({
phoneNumber && phoneNumber &&
candidateContacts.every(contact => contact.e164 !== phoneNumber.e164); candidateContacts.every(contact => contact.e164 !== phoneNumber.e164);
let username: string | undefined;
let isUsernameChecked = false;
let isUsernameVisible = false;
if (!phoneNumber && isUsernamesEnabled) {
username = getUsernameFromSearch(searchTerm);
isUsernameChecked = selectedContacts.some(
contact => contact.username === username
);
isUsernameVisible = candidateContacts.every(
contact => contact.username !== username
);
}
const inputRef = useRef<null | HTMLInputElement>(null); const inputRef = useRef<null | HTMLInputElement>(null);
const numberOfContactsAlreadyInGroup = conversationIdsAlreadyInGroup.size; const numberOfContactsAlreadyInGroup = conversationIdsAlreadyInGroup.size;
@ -157,19 +178,24 @@ export const ChooseGroupMembersModal: FunctionComponent<PropsType> = ({
if (filteredContacts.length) { if (filteredContacts.length) {
rowCount += filteredContacts.length; rowCount += filteredContacts.length;
} }
if (isPhoneNumberVisible) { if (isPhoneNumberVisible || isUsernameVisible) {
// "Contacts" header // "Contacts" header
if (filteredContacts.length) { if (filteredContacts.length) {
rowCount += 1; rowCount += 1;
} }
// "Find by phone number" + phone number // "Find by phone number" + phone number
// or
// "Find by username" + username
rowCount += 2; rowCount += 2;
} }
const getRow = (index: number): undefined | Row => { const getRow = (index: number): undefined | Row => {
let virtualIndex = index; let virtualIndex = index;
if (isPhoneNumberVisible && filteredContacts.length) { if (
(isPhoneNumberVisible || isUsernameVisible) &&
filteredContacts.length
) {
if (virtualIndex === 0) { if (virtualIndex === 0) {
return { return {
type: RowType.Header, type: RowType.Header,
@ -221,6 +247,24 @@ export const ChooseGroupMembersModal: FunctionComponent<PropsType> = ({
virtualIndex -= 2; virtualIndex -= 2;
} }
if (username) {
if (virtualIndex === 0) {
return {
type: RowType.Header,
i18nKey: 'findByUsernameHeader',
};
}
if (virtualIndex === 1) {
return {
type: RowType.UsernameCheckbox,
isChecked: isUsernameChecked,
isFetching: isFetchingByUsername(uuidFetchState, username),
username,
};
}
virtualIndex -= 2;
}
return undefined; return undefined;
}; };

View file

@ -110,6 +110,7 @@ const createProps = (hasGroupLink = false, expireTimer?: number): Props => ({
i18n={i18n} i18n={i18n}
lookupConversationWithoutUuid={makeFakeLookupConversationWithoutUuid()} lookupConversationWithoutUuid={makeFakeLookupConversationWithoutUuid()}
showUserNotFoundModal={action('showUserNotFoundModal')} showUserNotFoundModal={action('showUserNotFoundModal')}
isUsernamesEnabled
/> />
); );
}, },

View file

@ -30,6 +30,7 @@ export type ContactListItemConversationType = Pick<
| 'title' | 'title'
| 'type' | 'type'
| 'unblurredAvatarPath' | 'unblurredAvatarPath'
| 'username'
| 'e164' | 'e164'
>; >;

View file

@ -0,0 +1,84 @@
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { FunctionComponent } from 'react';
import React from 'react';
import { BaseConversationListItem } from './BaseConversationListItem';
import type { LocalizerType, ThemeType } from '../../types/Util';
import { AvatarColors } from '../../types/Colors';
import type { LookupConversationWithoutUuidActionsType } from '../../util/lookupConversationWithoutUuid';
export type PropsDataType = {
username: string;
isChecked: boolean;
isFetching: boolean;
};
type PropsHousekeepingType = {
i18n: LocalizerType;
theme: ThemeType;
toggleConversationInChooseMembers: (conversationId: string) => void;
} & LookupConversationWithoutUuidActionsType;
type PropsType = PropsDataType & PropsHousekeepingType;
export const UsernameCheckbox: FunctionComponent<PropsType> = React.memo(
function UsernameCheckbox({
username,
isChecked,
isFetching,
theme,
i18n,
lookupConversationWithoutUuid,
showUserNotFoundModal,
setIsFetchingUUID,
toggleConversationInChooseMembers,
}) {
const onClickItem = React.useCallback(async () => {
if (isFetching) {
return;
}
const conversationId = await lookupConversationWithoutUuid({
showUserNotFoundModal,
setIsFetchingUUID,
type: 'username',
username,
});
if (conversationId !== undefined) {
toggleConversationInChooseMembers(conversationId);
}
}, [
isFetching,
toggleConversationInChooseMembers,
lookupConversationWithoutUuid,
showUserNotFoundModal,
setIsFetchingUUID,
username,
]);
const title = i18n('at-username', { username });
return (
<BaseConversationListItem
acceptedMessageRequest={false}
checked={isChecked}
color={AvatarColors[0]}
conversationType="direct"
headerName={title}
i18n={i18n}
isMe={false}
isSelected={false}
isUsernameSearchResult
onClick={onClickItem}
shouldShowSpinner={isFetching}
theme={theme}
sharedGroupNames={[]}
title={title}
/>
);
}
);

View file

@ -18,10 +18,14 @@ import {
} from '../AddGroupMemberErrorDialog'; } from '../AddGroupMemberErrorDialog';
import { Button } from '../Button'; import { Button } from '../Button';
import type { LocalizerType } from '../../types/Util'; import type { LocalizerType } from '../../types/Util';
import { getUsernameFromSearch } from '../../types/Username';
import type { ParsedE164Type } from '../../util/libphonenumberInstance'; import type { ParsedE164Type } from '../../util/libphonenumberInstance';
import { parseAndFormatPhoneNumber } from '../../util/libphonenumberInstance'; import { parseAndFormatPhoneNumber } from '../../util/libphonenumberInstance';
import type { UUIDFetchStateType } from '../../util/uuidFetchState'; import type { UUIDFetchStateType } from '../../util/uuidFetchState';
import { isFetchingByE164 } from '../../util/uuidFetchState'; import {
isFetchingByUsername,
isFetchingByE164,
} from '../../util/uuidFetchState';
import { import {
getGroupSizeRecommendedLimit, getGroupSizeRecommendedLimit,
getGroupSizeHardLimit, getGroupSizeHardLimit,
@ -32,6 +36,7 @@ export type LeftPaneChooseGroupMembersPropsType = {
candidateContacts: ReadonlyArray<ConversationType>; candidateContacts: ReadonlyArray<ConversationType>;
isShowingRecommendedGroupSizeModal: boolean; isShowingRecommendedGroupSizeModal: boolean;
isShowingMaximumGroupSizeModal: boolean; isShowingMaximumGroupSizeModal: boolean;
isUsernamesEnabled: boolean;
searchTerm: string; searchTerm: string;
regionCode: string | undefined; regionCode: string | undefined;
selectedContacts: Array<ConversationType>; selectedContacts: Array<ConversationType>;
@ -42,6 +47,8 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
private readonly isPhoneNumberChecked: boolean; private readonly isPhoneNumberChecked: boolean;
private readonly isUsernameChecked: boolean;
private readonly isShowingMaximumGroupSizeModal: boolean; private readonly isShowingMaximumGroupSizeModal: boolean;
private readonly isShowingRecommendedGroupSizeModal: boolean; private readonly isShowingRecommendedGroupSizeModal: boolean;
@ -50,6 +57,8 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
private readonly phoneNumber: ParsedE164Type | undefined; private readonly phoneNumber: ParsedE164Type | undefined;
private readonly username: string | undefined;
private readonly selectedContacts: Array<ConversationType>; private readonly selectedContacts: Array<ConversationType>;
private readonly selectedConversationIdsSet: Set<string>; private readonly selectedConversationIdsSet: Set<string>;
@ -60,6 +69,7 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
candidateContacts, candidateContacts,
isShowingMaximumGroupSizeModal, isShowingMaximumGroupSizeModal,
isShowingRecommendedGroupSizeModal, isShowingRecommendedGroupSizeModal,
isUsernamesEnabled,
searchTerm, searchTerm,
regionCode, regionCode,
selectedContacts, selectedContacts,
@ -90,6 +100,22 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
} else { } else {
this.isPhoneNumberChecked = false; this.isPhoneNumberChecked = false;
} }
if (!this.phoneNumber && isUsernamesEnabled) {
const username = getUsernameFromSearch(searchTerm);
const isVisible = this.candidateContacts.every(
contact => contact.username !== username
);
if (isVisible) {
this.username = username;
}
this.isUsernameChecked = selectedContacts.some(
contact => contact.username === this.username
);
} else {
this.isUsernameChecked = false;
}
this.selectedContacts = selectedContacts; this.selectedContacts = selectedContacts;
this.selectedConversationIdsSet = new Set( this.selectedConversationIdsSet = new Set(
@ -246,6 +272,11 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
rowCount += 2; rowCount += 2;
} }
// Header + Username
if (this.username) {
rowCount += 2;
}
// Header + Contacts // Header + Contacts
if (this.candidateContacts.length) { if (this.candidateContacts.length) {
rowCount += 1 + this.candidateContacts.length; rowCount += 1 + this.candidateContacts.length;
@ -260,7 +291,7 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
} }
getRow(actualRowIndex: number): undefined | Row { getRow(actualRowIndex: number): undefined | Row {
if (!this.candidateContacts.length && !this.phoneNumber) { if (!this.candidateContacts.length && !this.phoneNumber && !this.username) {
return undefined; return undefined;
} }
@ -322,6 +353,24 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
virtualRowIndex -= 2; virtualRowIndex -= 2;
} }
if (this.username) {
if (virtualRowIndex === 0) {
return {
type: RowType.Header,
i18nKey: 'findByUsernameHeader',
};
}
if (virtualRowIndex === 1) {
return {
type: RowType.UsernameCheckbox,
isChecked: this.isUsernameChecked,
isFetching: isFetchingByUsername(this.uuidFetchState, this.username),
username: this.username,
};
}
virtualRowIndex -= 2;
}
return undefined; return undefined;
} }

View file

@ -43,14 +43,16 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
private readonly uuidFetchState: UUIDFetchStateType; private readonly uuidFetchState: UUIDFetchStateType;
private readonly isUsernamesEnabled: boolean;
private readonly searchTerm: string; private readonly searchTerm: string;
private readonly phoneNumber: ParsedE164Type | undefined; private readonly phoneNumber: ParsedE164Type | undefined;
private readonly isPhoneNumberVisible: boolean; private readonly isPhoneNumberVisible: boolean;
private readonly username: string | undefined;
private readonly isUsernameVisible: boolean;
constructor({ constructor({
composeContacts, composeContacts,
composeGroups, composeGroups,
@ -74,7 +76,18 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
this.isPhoneNumberVisible = false; this.isPhoneNumberVisible = false;
} }
this.uuidFetchState = uuidFetchState; this.uuidFetchState = uuidFetchState;
this.isUsernamesEnabled = isUsernamesEnabled;
if (isUsernamesEnabled && !this.phoneNumber) {
this.username = getUsernameFromSearch(this.searchTerm);
this.isUsernameVisible =
isUsernamesEnabled &&
Boolean(this.username) &&
this.composeContacts.every(
contact => contact.username !== this.username
);
} else {
this.isUsernameVisible = false;
}
} }
override getHeaderContents({ override getHeaderContents({
@ -148,7 +161,7 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
if (this.hasGroupsHeader()) { if (this.hasGroupsHeader()) {
result += 1; result += 1;
} }
if (this.getUsernameFromSearch()) { if (this.isUsernameVisible) {
result += 2; result += 2;
} }
if (this.isPhoneNumberVisible) { if (this.isPhoneNumberVisible) {
@ -218,8 +231,7 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
virtualRowIndex -= this.composeGroups.length; virtualRowIndex -= this.composeGroups.length;
} }
const username = this.getUsernameFromSearch(); if (this.username && this.isUsernameVisible) {
if (username) {
if (virtualRowIndex === 0) { if (virtualRowIndex === 0) {
return { return {
type: RowType.Header, type: RowType.Header,
@ -232,10 +244,10 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
if (virtualRowIndex === 0) { if (virtualRowIndex === 0) {
return { return {
type: RowType.UsernameSearchResult, type: RowType.UsernameSearchResult,
username, username: this.username,
isFetchingUsername: isFetchingByUsername( isFetchingUsername: isFetchingByUsername(
this.uuidFetchState, this.uuidFetchState,
username this.username
), ),
}; };
@ -295,7 +307,8 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
currHeaderIndices.top !== prevHeaderIndices.top || currHeaderIndices.top !== prevHeaderIndices.top ||
currHeaderIndices.contact !== prevHeaderIndices.contact || currHeaderIndices.contact !== prevHeaderIndices.contact ||
currHeaderIndices.group !== prevHeaderIndices.group || currHeaderIndices.group !== prevHeaderIndices.group ||
currHeaderIndices.username !== prevHeaderIndices.username currHeaderIndices.username !== prevHeaderIndices.username ||
currHeaderIndices.phoneNumber !== prevHeaderIndices.phoneNumber
); );
} }
@ -318,31 +331,17 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
return Boolean(this.composeGroups.length); return Boolean(this.composeGroups.length);
} }
private getUsernameFromSearch(): string | undefined {
if (!this.isUsernamesEnabled) {
return undefined;
}
if (this.phoneNumber) {
return undefined;
}
if (this.searchTerm) {
return getUsernameFromSearch(this.searchTerm);
}
return undefined;
}
private getHeaderIndices(): { private getHeaderIndices(): {
top?: number; top?: number;
contact?: number; contact?: number;
group?: number; group?: number;
phoneNumber?: number;
username?: number; username?: number;
} { } {
let top: number | undefined; let top: number | undefined;
let contact: number | undefined; let contact: number | undefined;
let group: number | undefined; let group: number | undefined;
let phoneNumber: number | undefined;
let username: number | undefined; let username: number | undefined;
let rowCount = 0; let rowCount = 0;
@ -359,7 +358,10 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
group = rowCount; group = rowCount;
rowCount += this.composeContacts.length; rowCount += this.composeContacts.length;
} }
if (this.getUsernameFromSearch()) { if (this.phoneNumber) {
phoneNumber = rowCount;
}
if (this.username) {
username = rowCount; username = rowCount;
} }
@ -367,6 +369,7 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
top, top,
contact, contact,
group, group,
phoneNumber,
username, username,
}; };
} }

View file

@ -12,6 +12,7 @@ import type { StatePropsType } from '../../components/conversation/conversation-
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 { getUsernamesEnabled } from '../selectors/items';
import { import {
getCandidateContactsForNewGroup, getCandidateContactsForNewGroup,
getConversationByIdSelector, getConversationByIdSelector,
@ -55,6 +56,7 @@ const mapStateToProps = (
theme: getTheme(state), theme: getTheme(state),
selectedContacts, selectedContacts,
lookupConversationWithoutUuid, lookupConversationWithoutUuid,
isUsernamesEnabled: getUsernamesEnabled(state),
}; };
}; };

View file

@ -156,6 +156,7 @@ const getModeSpecificProps = (
regionCode: getRegionCode(state), regionCode: getRegionCode(state),
searchTerm: getComposerConversationSearchTerm(state), searchTerm: getComposerConversationSearchTerm(state),
selectedContacts: getComposeSelectedContacts(state), selectedContacts: getComposeSelectedContacts(state),
isUsernamesEnabled: getUsernamesEnabled(state),
uuidFetchState: getComposerUUIDFetchState(state), uuidFetchState: getComposerUUIDFetchState(state),
}; };
case ComposerStep.SetGroupMetadata: case ComposerStep.SetGroupMetadata:

View file

@ -17,6 +17,7 @@ describe('LeftPaneChooseGroupMembersHelper', () => {
candidateContacts: [], candidateContacts: [],
isShowingRecommendedGroupSizeModal: false, isShowingRecommendedGroupSizeModal: false,
isShowingMaximumGroupSizeModal: false, isShowingMaximumGroupSizeModal: false,
isUsernamesEnabled: true,
searchTerm: '', searchTerm: '',
regionCode: 'US', regionCode: 'US',
selectedContacts: [], selectedContacts: [],
@ -62,6 +63,7 @@ describe('LeftPaneChooseGroupMembersHelper', () => {
candidateContacts: [], candidateContacts: [],
searchTerm: 'foo bar', searchTerm: 'foo bar',
selectedContacts: [getDefaultConversation()], selectedContacts: [getDefaultConversation()],
isUsernamesEnabled: false,
}).getRowCount(), }).getRowCount(),
0 0
); );
@ -107,6 +109,7 @@ describe('LeftPaneChooseGroupMembersHelper', () => {
candidateContacts: [], candidateContacts: [],
searchTerm: 'foo bar', searchTerm: 'foo bar',
selectedContacts: [getDefaultConversation()], selectedContacts: [getDefaultConversation()],
isUsernamesEnabled: false,
}).getRow(0) }).getRow(0)
); );
}); });
@ -120,6 +123,7 @@ describe('LeftPaneChooseGroupMembersHelper', () => {
...defaults, ...defaults,
candidateContacts, candidateContacts,
searchTerm: 'foo bar', searchTerm: 'foo bar',
isUsernamesEnabled: false,
selectedContacts: [candidateContacts[1]], selectedContacts: [candidateContacts[1]],
}); });
@ -164,5 +168,51 @@ describe('LeftPaneChooseGroupMembersHelper', () => {
disabledReason: undefined, disabledReason: undefined,
}); });
}); });
it('returns a header, then the phone number, then a blank space if there are contacts', () => {
const helper = new LeftPaneChooseGroupMembersHelper({
...defaults,
candidateContacts: [],
searchTerm: '212 555',
selectedContacts: [],
});
assert.deepEqual(helper.getRow(0), {
type: RowType.Header,
i18nKey: 'findByPhoneNumberHeader',
});
assert.deepEqual(helper.getRow(1), {
type: RowType.PhoneNumberCheckbox,
phoneNumber: {
isValid: false,
userInput: '212 555',
e164: '+1212555',
},
isChecked: false,
isFetching: false,
});
assert.deepEqual(helper.getRow(2), { type: RowType.Blank });
});
it('returns a header, then the username, then a blank space if there are contacts', () => {
const helper = new LeftPaneChooseGroupMembersHelper({
...defaults,
candidateContacts: [],
searchTerm: 'signal',
selectedContacts: [],
});
assert.deepEqual(helper.getRow(0), {
type: RowType.Header,
i18nKey: 'findByUsernameHeader',
});
assert.deepEqual(helper.getRow(1), {
type: RowType.UsernameCheckbox,
username: 'signal',
isChecked: false,
isFetching: false,
});
assert.deepEqual(helper.getRow(2), { type: RowType.Blank });
});
}); });
}); });

View file

@ -1626,7 +1626,7 @@ export function initialize({
return (await _ajax({ return (await _ajax({
call: 'profile', call: 'profile',
httpType: 'GET', httpType: 'GET',
urlParameters: `username/${usernameToFetch}`, urlParameters: `/username/${usernameToFetch}`,
responseType: 'json', responseType: 'json',
redactUrl: _createRedactor(usernameToFetch), redactUrl: _createRedactor(usernameToFetch),
})) as ProfileType; })) as ProfileType;