Skip to content

Commit

Permalink
Merge pull request #39757 from Expensify/feature-groupChats2
Browse files Browse the repository at this point in the history
[Group Chats] Add remaining features
  • Loading branch information
marcaaron authored Apr 12, 2024
2 parents dcee26b + f5970a9 commit 5602b63
Show file tree
Hide file tree
Showing 52 changed files with 1,729 additions and 355 deletions.
7 changes: 4 additions & 3 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,7 @@ const CONST = {
UNAPPROVED: 'UNAPPROVED', // OldDot Action
UNHOLD: 'UNHOLD',
UNSHARE: 'UNSHARE', // OldDot Action
UPDATEGROUPCHATMEMBERROLE: 'UPDATEGROUPCHATMEMBERROLE',
POLICYCHANGELOG: {
ADD_APPROVER_RULE: 'POLICYCHANGELOG_ADD_APPROVER_RULE',
ADD_BUDGET: 'POLICYCHANGELOG_ADD_BUDGET',
Expand Down Expand Up @@ -1862,17 +1863,17 @@ const CONST = {

MAX_THREAD_REPLIES_PREVIEW: 99,

// Character Limits
FORM_CHARACTER_LIMIT: 50,
LEGAL_NAMES_CHARACTER_LIMIT: 150,
LOGIN_CHARACTER_LIMIT: 254,
CATEGORY_NAME_LIMIT: 256,

TAG_NAME_LIMIT: 256,

REPORT_NAME_LIMIT: 256,
TITLE_CHARACTER_LIMIT: 100,
DESCRIPTION_LIMIT: 500,

WORKSPACE_NAME_CHARACTER_LIMIT: 80,

AVATAR_CROP_MODAL: {
// The next two constants control what is min and max value of the image crop scale.
// Values define in how many times the image can be bigger than its container.
Expand Down
3 changes: 3 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,8 @@ const ONYXKEYS = {
WORKSPACE_TAX_NAME_FORM_DRAFT: 'workspaceTaxNameFormDraft',
WORKSPACE_TAX_VALUE_FORM: 'workspaceTaxValueForm',
WORKSPACE_TAX_VALUE_FORM_DRAFT: 'workspaceTaxValueFormDraft',
NEW_CHAT_NAME_FORM: 'newChatNameForm',
NEW_CHAT_NAME_FORM_DRAFT: 'newChatNameFormDraft',
},
} as const;

Expand Down Expand Up @@ -511,6 +513,7 @@ type OnyxFormValuesMapping = {
[ONYXKEYS.FORMS.POLICY_DISTANCE_RATE_EDIT_FORM]: FormTypes.PolicyDistanceRateEditForm;
[ONYXKEYS.FORMS.WORKSPACE_TAX_NAME_FORM]: FormTypes.WorkspaceTaxNameForm;
[ONYXKEYS.FORMS.WORKSPACE_TAX_VALUE_FORM]: FormTypes.WorkspaceTaxValueForm;
[ONYXKEYS.FORMS.NEW_CHAT_NAME_FORM]: FormTypes.NewChatNameForm;
};

type OnyxFormDraftValuesMapping = {
Expand Down
17 changes: 17 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ const ROUTES = {
NEW: 'new',
NEW_CHAT: 'new/chat',
NEW_CHAT_CONFIRM: 'new/chat/confirm',
NEW_CHAT_EDIT_NAME: 'new/chat/confirm/name/edit',
NEW_ROOM: 'new/room',

REPORT: 'r',
Expand Down Expand Up @@ -226,6 +227,18 @@ const ROUTES = {
route: 'r/:reportID/participants',
getRoute: (reportID: string) => `r/${reportID}/participants` as const,
},
REPORT_PARTICIPANTS_INVITE: {
route: 'r/:reportID/participants/invite',
getRoute: (reportID: string) => `r/${reportID}/participants/invite` as const,
},
REPORT_PARTICIPANTS_DETAILS: {
route: 'r/:reportID/participants/:accountID',
getRoute: (reportID: string, accountID: number) => `r/${reportID}/participants/${accountID}` as const,
},
REPORT_PARTICIPANTS_ROLE_SELECTION: {
route: 'r/:reportID/participants/:accountID/role',
getRoute: (reportID: string, accountID: number) => `r/${reportID}/participants/${accountID}/role` as const,
},
REPORT_WITH_ID_DETAILS: {
route: 'r/:reportID/details',
getRoute: (reportID: string, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/details`, backTo),
Expand All @@ -238,6 +251,10 @@ const ROUTES = {
route: 'r/:reportID/settings/room-name',
getRoute: (reportID: string) => `r/${reportID}/settings/room-name` as const,
},
REPORT_SETTINGS_GROUP_NAME: {
route: 'r/:reportID/settings/group-name',
getRoute: (reportID: string) => `r/${reportID}/settings/group-name` as const,
},
REPORT_SETTINGS_NOTIFICATION_PREFERENCES: {
route: 'r/:reportID/settings/notification-preferences',
getRoute: (reportID: string) => `r/${reportID}/settings/notification-preferences` as const,
Expand Down
9 changes: 8 additions & 1 deletion src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ const SCREENS = {
REPORT_SETTINGS: {
ROOT: 'Report_Settings_Root',
ROOM_NAME: 'Report_Settings_Room_Name',
GROUP_NAME: 'Report_Settings_Group_Name',
NOTIFICATION_PREFERENCES: 'Report_Settings_Notification_Preferences',
WRITE_CAPABILITY: 'Report_Settings_Write_Capability',
VISIBILITY: 'Report_Settings_Visibility',
Expand Down Expand Up @@ -273,6 +274,7 @@ const SCREENS = {
ROOT: 'NewChat_Root',
NEW_CHAT: 'chat',
NEW_CHAT_CONFIRM: 'NewChat_Confirm',
NEW_CHAT_EDIT_NAME: 'NewChat_Edit_Name',
NEW_ROOM: 'room',
},

Expand Down Expand Up @@ -309,7 +311,12 @@ const SCREENS = {
PROFILE_ROOT: 'Profile_Root',
PROCESS_MONEY_REQUEST_HOLD_ROOT: 'ProcessMoneyRequestHold_Root',
REPORT_DESCRIPTION_ROOT: 'Report_Description_Root',
REPORT_PARTICIPANTS_ROOT: 'ReportParticipants_Root',
REPORT_PARTICIPANTS: {
ROOT: 'ReportParticipants_Root',
INVITE: 'ReportParticipants_Invite',
DETAILS: 'ReportParticipants_Details',
ROLE: 'ReportParticipants_Role',
},
ROOM_MEMBERS_ROOT: 'RoomMembers_Root',
ROOM_INVITE_ROOT: 'RoomInvite_Root',
SEARCH_ROOT: 'Search_Root',
Expand Down
14 changes: 11 additions & 3 deletions src/components/AvatarWithImagePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@ type AvatarWithImagePickerProps = {

/** Allows to open an image without Attachment Picker. */
enablePreview?: boolean;

/** Hard disables the "View photo" option */
shouldDisableViewPhoto?: boolean;

/** Optionally override the default "Edit" icon */
editIcon?: IconAsset;
};

function AvatarWithImagePicker({
Expand Down Expand Up @@ -144,6 +150,8 @@ function AvatarWithImagePicker({
disabled = false,
onViewPhotoPress,
enablePreview = false,
shouldDisableViewPhoto = false,
editIcon = Expensicons.Pencil,
}: AvatarWithImagePickerProps) {
const theme = useTheme();
const styles = useThemeStyles();
Expand Down Expand Up @@ -332,7 +340,7 @@ function AvatarWithImagePicker({
{!disabled && (
<View style={StyleSheet.flatten([styles.smallEditIcon, styles.smallAvatarEditIcon, editIconStyle])}>
<Icon
src={Expensicons.Pencil}
src={editIcon}
width={variables.iconSizeSmall}
height={variables.iconSizeSmall}
fill={theme.icon}
Expand All @@ -354,8 +362,8 @@ function AvatarWithImagePicker({
{({openPicker}) => {
const menuItems = createMenuItems(openPicker);

// If the current avatar isn't a default avatar, allow the "View Photo" option
if (!isUsingDefaultAvatar) {
// If the current avatar isn't a default avatar and we are not overriding this behavior allow the "View Photo" option
if (!shouldDisableViewPhoto && !isUsingDefaultAvatar) {
menuItems.push({
icon: Expensicons.Eye,
text: translate('avatarWithImagePicker.viewPhoto'),
Expand Down
64 changes: 64 additions & 0 deletions src/components/ChatDetailsQuickActionsBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React, {useState} from 'react';
import {View} from 'react-native';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import * as Report from '@userActions/Report';
import type {Report as OnyxReportType} from '@src/types/onyx';
import Button from './Button';
import ConfirmModal from './ConfirmModal';
import * as Expensicons from './Icon/Expensicons';

type ChatDetailsQuickActionsBarProps = {
report: OnyxReportType;
};

function ChatDetailsQuickActionsBar({report}: ChatDetailsQuickActionsBarProps) {
const styles = useThemeStyles();
const [isLastMemberLeavingGroupModalVisible, setIsLastMemberLeavingGroupModalVisible] = useState(false);
const {translate} = useLocalize();
const isPinned = !!report.isPinned;
return (
<View style={[styles.flexRow, styles.ph5, styles.mb5]}>
<View style={[styles.flex1, styles.pr3]}>
<ConfirmModal
danger
title={translate('groupChat.lastMemberTitle')}
isVisible={isLastMemberLeavingGroupModalVisible}
onConfirm={() => {
setIsLastMemberLeavingGroupModalVisible(false);
Report.leaveGroupChat(report.reportID);
}}
onCancel={() => setIsLastMemberLeavingGroupModalVisible(false)}
prompt={translate('groupChat.lastMemberWarning')}
confirmText={translate('common.leave')}
cancelText={translate('common.cancel')}
/>
<Button
onPress={() => {
if (Object.keys(report?.participants ?? {}).length === 1) {
setIsLastMemberLeavingGroupModalVisible(true);
return;
}

Report.leaveGroupChat(report.reportID);
}}
icon={Expensicons.Exit}
style={styles.flex1}
text={translate('common.leave')}
/>
</View>
<View style={[styles.flex1]}>
<Button
onPress={() => Report.togglePinnedState(report.reportID, isPinned)}
icon={Expensicons.Pin}
style={styles.flex1}
text={isPinned ? translate('common.unPin') : translate('common.pin')}
/>
</View>
</View>
);
}

ChatDetailsQuickActionsBar.displayName = 'ChatDetailsQuickActionsBar';

export default ChatDetailsQuickActionsBar;
4 changes: 2 additions & 2 deletions src/components/FormAlertWithSubmitButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ type FormAlertWithSubmitButtonProps = {
buttonStyles?: StyleProp<ViewStyle>;

/** Whether to show the alert text */
isAlertVisible: boolean;
isAlertVisible?: boolean;

/** Text for the button */
buttonText: string;
Expand All @@ -69,7 +69,7 @@ function FormAlertWithSubmitButton({
footerContent,
buttonStyles,
buttonText,
isAlertVisible,
isAlertVisible = false,
onSubmit,
useSmallerSubmitButtonSize = false,
errorMessageStyle,
Expand Down
32 changes: 20 additions & 12 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type {ReactNode} from 'react';
import React from 'react';
import React, {useMemo} from 'react';
import type {StyleProp, TextStyle, ViewStyle} from 'react-native';
import {View} from 'react-native';
import useThemeStyles from '@hooks/useThemeStyles';
Expand All @@ -25,6 +25,24 @@ type HeaderProps = {

function Header({title = '', subtitle = '', textStyles = [], containerStyles = [], shouldShowEnvironmentBadge = false}: HeaderProps) {
const styles = useThemeStyles();
const renderedSubtitle = useMemo(
() => (
<>
{/* If there's no subtitle then display a fragment to avoid an empty space which moves the main title */}
{typeof subtitle === 'string'
? Boolean(subtitle) && (
<Text
style={[styles.mutedTextLabel, styles.pre]}
numberOfLines={1}
>
{subtitle}
</Text>
)
: subtitle}
</>
),
[subtitle, styles],
);
return (
<View style={[styles.flex1, styles.flexRow, containerStyles]}>
<View style={styles.mw100}>
Expand All @@ -38,17 +56,7 @@ function Header({title = '', subtitle = '', textStyles = [], containerStyles = [
</Text>
)
: title}
{/* If there's no subtitle then display a fragment to avoid an empty space which moves the main title */}
{typeof subtitle === 'string'
? Boolean(subtitle) && (
<Text
style={[styles.mutedTextLabel, styles.pre]}
numberOfLines={1}
>
{subtitle}
</Text>
)
: subtitle}
{renderedSubtitle}
</View>
{shouldShowEnvironmentBadge && <EnvironmentBadge />}
</View>
Expand Down
6 changes: 3 additions & 3 deletions src/components/LHNOptionsList/OptionRowLHN.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,7 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti

const isGroupChat = ReportUtils.isGroupChat(optionItem) || ReportUtils.isDeprecatedGroupDM(optionItem);

const fullTitle = isGroupChat ? ReportUtils.getGroupChatName(report?.participantAccountIDs ?? []) : optionItem.text;

const fullTitle = isGroupChat ? ReportUtils.getGroupChatName(undefined, false, optionItem.reportID ?? '') : optionItem.text;
const subscriptAvatarBorderColor = isFocused ? focusedBackgroundColor : theme.sidebar;
return (
<OfflineWithFeedback
Expand Down Expand Up @@ -202,7 +201,8 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti
!!optionItem.isPolicyExpenseChat ||
!!optionItem.isTaskReport ||
!!optionItem.isThread ||
!!optionItem.isMoneyRequestReport
!!optionItem.isMoneyRequestReport ||
ReportUtils.isGroupChat(report)
}
/>
{isStatusVisible && (
Expand Down
7 changes: 1 addition & 6 deletions src/components/RoomHeaderAvatars.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,10 @@ import Text from './Text';
type RoomHeaderAvatarsProps = {
icons: Icon[];
reportID: string;
isGroupChat?: boolean;
};

function RoomHeaderAvatars({icons, reportID, isGroupChat}: RoomHeaderAvatarsProps) {
function RoomHeaderAvatars({icons, reportID}: RoomHeaderAvatarsProps) {
const navigateToAvatarPage = (icon: Icon) => {
if (isGroupChat) {
return;
}

if (icon.type === CONST.ICON_TYPE_WORKSPACE) {
Navigation.navigate(ROUTES.REPORT_AVATAR.getRoute(reportID));
return;
Expand Down
26 changes: 14 additions & 12 deletions src/components/SelectionList/InviteMemberListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,17 +89,19 @@ function InviteMemberListItem({
/>
))}
<View style={[styles.flex1, styles.flexColumn, styles.justifyContentCenter, styles.alignItemsStretch, styles.optionRow]}>
<TextWithTooltip
shouldShowTooltip={showTooltip}
text={Str.removeSMSDomain(item.text ?? '')}
style={[
styles.optionDisplayName,
isFocused ? styles.sidebarLinkActiveText : styles.sidebarLinkText,
item.isBold !== false && styles.sidebarLinkTextBold,
styles.pre,
item.alternateText ? styles.mb1 : null,
]}
/>
<View style={[styles.flexRow, styles.alignItemsCenter]}>
<TextWithTooltip
shouldShowTooltip={showTooltip}
text={Str.removeSMSDomain(item.text ?? '')}
style={[
styles.optionDisplayName,
isFocused ? styles.sidebarLinkActiveText : styles.sidebarLinkText,
item.isBold !== false && styles.sidebarLinkTextBold,
styles.pre,
item.alternateText ? styles.mb1 : null,
]}
/>
</View>
{!!item.alternateText && (
<TextWithTooltip
shouldShowTooltip={showTooltip}
Expand All @@ -109,7 +111,7 @@ function InviteMemberListItem({
)}
</View>
{!!item.rightElement && item.rightElement}
{canSelectMultiple && (
{canSelectMultiple && !item.isDisabled && (
<PressableWithFeedback
onPress={handleCheckboxPress}
disabled={isDisabled}
Expand Down
3 changes: 3 additions & 0 deletions src/components/SelectionList/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ type ListItem = {
/** The search value from the selection list */
searchText?: string | null;

/** What text to show inside the badge (if none present the badge will be omitted) */
badgeText?: string;

brickRoadIndicator?: BrickRoad | '' | null;
};

Expand Down
Loading

0 comments on commit 5602b63

Please sign in to comment.