Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
193 changes: 109 additions & 84 deletions src/pages/NewChatConfirmPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
import type {RefObject} from 'react';
import React, {useEffect, useRef, useState} from 'react';
import {View} from 'react-native';
import AvatarWithImagePicker from '@components/AvatarWithImagePicker';
import Badge from '@components/Badge';
Expand All @@ -10,6 +11,7 @@ import InviteMemberListItem from '@components/SelectionList/ListItem/InviteMembe
import type {ListItem} from '@components/SelectionList/types';
import Text from '@components/Text';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useIsInLandscapeMode from '@hooks/useIsInLandscapeMode';
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
Expand All @@ -36,86 +38,18 @@ function navigateToEditChatName() {
Navigation.navigate(ROUTES.NEW_CHAT_EDIT_NAME);
}

function NewChatConfirmPage() {
const optimisticReportID = useRef<string>(generateReportID());
const [avatarFile, setAvatarFile] = useState<File | CustomRNImageManipulatorResult | undefined>();
const {translate, localeCompare, formatPhoneNumber} = useLocalize();
type AvatarAndGroupNameSectionProps = {
setAvatarFile: (avatarFile: File | CustomRNImageManipulatorResult | undefined) => void;
optimisticReportID: RefObject<string>;
};

function AvatarAndGroupNameSection({setAvatarFile, optimisticReportID}: AvatarAndGroupNameSectionProps) {
const {translate, formatPhoneNumber} = useLocalize();
const styles = useThemeStyles();
const personalData = useCurrentUserPersonalDetails();
const [newGroupDraft, newGroupDraftMetaData] = useOnyx(ONYXKEYS.NEW_GROUP_CHAT_DRAFT);
const [allPersonalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST);
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
const [betas] = useOnyx(ONYXKEYS.BETAS);
const [isSelfTourViewed] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasSeenTourSelector});

const icons = useMemoizedLazyExpensifyIcons(['Camera']);

const selectedOptions = useMemo((): Participant[] => {
if (!newGroupDraft?.participants) {
return [];
}
const options: Participant[] = newGroupDraft.participants.map((participant) =>
getParticipantsOption({accountID: participant.accountID, login: participant?.login, reportID: ''}, allPersonalDetails),
);
return options;
}, [allPersonalDetails, newGroupDraft?.participants]);

const groupName = newGroupDraft?.reportName ? newGroupDraft?.reportName : getGroupChatName(formatPhoneNumber, newGroupDraft?.participants);
const selectedParticipants: ListItem[] = useMemo(
() =>
selectedOptions
.map((selectedOption: Participant) => {
const accountID = selectedOption.accountID;
const isAdmin = personalData.accountID === accountID;
const section: ListItem = {
login: selectedOption?.login ?? '',
text: selectedOption?.text ?? '',
keyForList: selectedOption?.keyForList ?? '',
isSelected: !isAdmin,
isDisabled: isAdmin,
accountID,
icons: selectedOption?.icons,
alternateText: selectedOption?.login ?? '',
rightElement: isAdmin ? <Badge text={translate('common.admin')} /> : undefined,
};
return section;
})
.sort((a, b) => localeCompare(a.text?.toLowerCase() ?? '', b.text?.toLowerCase() ?? '')),
[selectedOptions, personalData.accountID, translate, localeCompare],
);

/**
* Removes a selected option from list if already selected.
*/
const unselectOption = useCallback(
(option: ListItem) => {
if (!newGroupDraft) {
return;
}
const newSelectedParticipants = (newGroupDraft.participants ?? []).filter((participant) => participant?.login !== option.login);
setGroupDraft({participants: newSelectedParticipants});
},
[newGroupDraft],
);

const createGroup = useCallback(() => {
if (!newGroupDraft) {
return;
}

const logins: string[] = (newGroupDraft.participants ?? []).map((participant) => participant.login).filter((login): login is string => !!login);
navigateToAndCreateGroupChat(
logins,
newGroupDraft.reportName ?? '',
personalData.login ?? '',
optimisticReportID.current,
introSelected,
isSelfTourViewed,
betas,
newGroupDraft.avatarUri ?? '',
avatarFile,
);
}, [newGroupDraft, avatarFile, personalData.login, introSelected, betas, isSelfTourViewed]);

const stashedLocalAvatarImage = newGroupDraft?.avatarUri;

Expand All @@ -142,11 +76,7 @@ function NewChatConfirmPage() {
}, [newGroupDraftMetaData]);

return (
<ScreenWrapper testID="NewChatConfirmPage">
<HeaderWithBackButton
title={translate('common.group')}
onBackButtonPress={navigateBack}
/>
<>
<View style={styles.avatarSectionWrapper}>
<AvatarWithImagePicker
isUsingDefaultAvatar={!stashedLocalAvatarImage}
Expand Down Expand Up @@ -174,6 +104,92 @@ function NewChatConfirmPage() {
description={translate('newRoomPage.groupName')}
wrapperStyle={[styles.ph4]}
/>
</>
);
}

function NewChatConfirmPage() {
const isInLandscapeMode = useIsInLandscapeMode();
const optimisticReportID = useRef<string>(generateReportID());
const [avatarFile, setAvatarFile] = useState<File | CustomRNImageManipulatorResult | undefined>();
const {translate, localeCompare} = useLocalize();
const styles = useThemeStyles();
const personalData = useCurrentUserPersonalDetails();
const [allPersonalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST);
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
const [betas] = useOnyx(ONYXKEYS.BETAS);
const [isSelfTourViewed] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasSeenTourSelector});
const [newGroupDraft] = useOnyx(ONYXKEYS.NEW_GROUP_CHAT_DRAFT);

const participants = newGroupDraft?.participants ?? [];

const selectedOptions: Participant[] = participants.map((participant) =>
getParticipantsOption({accountID: participant.accountID, login: participant?.login, reportID: ''}, allPersonalDetails),
);

const selectedParticipants: ListItem[] = selectedOptions
.map((selectedOption: Participant) => {
const accountID = selectedOption.accountID;
const isAdmin = personalData.accountID === accountID;
const section: ListItem = {
login: selectedOption?.login ?? '',
text: selectedOption?.text ?? '',
keyForList: selectedOption?.keyForList ?? '',
isSelected: !isAdmin,
isDisabled: isAdmin,
accountID,
icons: selectedOption?.icons,
alternateText: selectedOption?.login ?? '',
rightElement: isAdmin ? <Badge text={translate('common.admin')} /> : undefined,
};
return section;
})
.sort((a, b) => localeCompare(a.text?.toLowerCase() ?? '', b.text?.toLowerCase() ?? ''));

/**
* Removes a selected option from list if already selected.
*/
const unselectOption = (option: ListItem) => {
if (!newGroupDraft) {
return;
}
const newSelectedParticipants = (newGroupDraft.participants ?? []).filter((participant) => participant?.login !== option.login);
setGroupDraft({participants: newSelectedParticipants});
};

const createGroup = () => {
if (!newGroupDraft) {
return;
}

const logins: string[] = (newGroupDraft.participants ?? []).map((participant) => participant.login).filter((login): login is string => !!login);
navigateToAndCreateGroupChat(
logins,
newGroupDraft.reportName ?? '',
personalData.login ?? '',
optimisticReportID.current,
introSelected,
isSelfTourViewed,
betas,
newGroupDraft.avatarUri ?? '',
avatarFile,
);
};

return (
<ScreenWrapper testID="NewChatConfirmPage">
<HeaderWithBackButton
title={translate('common.group')}
onBackButtonPress={navigateBack}
/>

{!isInLandscapeMode && (
<AvatarAndGroupNameSection
setAvatarFile={setAvatarFile}
optimisticReportID={optimisticReportID}
/>
)}

<View style={[styles.flex1, styles.mt3]}>
<SelectionList
data={selectedParticipants}
Expand All @@ -185,10 +201,19 @@ function NewChatConfirmPage() {
text: translate('newChatPage.startGroup'),
onConfirm: createGroup,
}}
shouldHeaderBeInsideList={isInLandscapeMode}
customListHeader={
<View style={[styles.mt8, styles.mb4, styles.justifyContentCenter]}>
<Text style={[styles.ph5, styles.textLabelSupporting]}>{translate('common.members')}</Text>
</View>
<>
{isInLandscapeMode && (
<AvatarAndGroupNameSection
setAvatarFile={setAvatarFile}
optimisticReportID={optimisticReportID}
/>
)}
<View style={[styles.mt8, styles.mb4, styles.justifyContentCenter]}>
<Text style={[styles.ph5, styles.textLabelSupporting]}>{translate('common.members')}</Text>
</View>
</>
}
/>
</View>
Expand Down
7 changes: 5 additions & 2 deletions src/pages/ReportParticipantDetailsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import MenuItem from '@components/MenuItem';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
import ScreenWrapper from '@components/ScreenWrapper';
import ScrollView from '@components/ScrollView';
import Text from '@components/Text';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useIsInLandscapeMode from '@hooks/useIsInLandscapeMode';
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
Expand All @@ -34,6 +36,7 @@ type ReportParticipantDetailsPageProps = WithReportOrNotFoundProps & PlatformSta

function ReportParticipantDetails({report, route}: ReportParticipantDetailsPageProps) {
const icons = useMemoizedLazyExpensifyIcons(['RemoveMembers', 'Info']);
const isInLandscapeMode = useIsInLandscapeMode();
const styles = useThemeStyles();
const {formatPhoneNumber, translate} = useLocalize();
const StyleUtils = useStyleUtils();
Expand Down Expand Up @@ -75,7 +78,7 @@ function ReportParticipantDetails({report, route}: ReportParticipantDetailsPageP
title={displayName}
onBackButtonPress={() => Navigation.goBack(backTo)}
/>
<View style={[styles.containerWithSpaceBetween, styles.pointerEventsBoxNone, styles.justifyContentStart]}>
<ScrollView contentContainerStyle={[!isInLandscapeMode && [styles.containerWithSpaceBetween, styles.justifyContentStart], styles.pointerEventsBoxNone]}>
<View style={[styles.avatarSectionWrapper, styles.pb0]}>
<Avatar
containerStyles={[styles.avatarXLarge, styles.mv5, styles.noOutline]}
Expand Down Expand Up @@ -136,7 +139,7 @@ function ReportParticipantDetails({report, route}: ReportParticipantDetailsPageP
shouldShowRightIcon
/>
</View>
</View>
</ScrollView>
</ScreenWrapper>
);
}
Expand Down
Loading