Skip to content

Commit

Permalink
Merge pull request Expensify#50281 from bernhardoj/fix/49086-task-tit…
Browse files Browse the repository at this point in the history
…le-overflown

#2 - Fix task title is overflown and align the checkbox and arrow correctly
  • Loading branch information
arosiclair authored Oct 7, 2024
2 parents 96957ef + de31219 commit fb4e24e
Show file tree
Hide file tree
Showing 5 changed files with 43 additions and 24 deletions.
37 changes: 22 additions & 15 deletions src/components/ReportActionItem/TaskPreview.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import {Str} from 'expensify-common';
import React from 'react';
import {View} from 'react-native';
import type {StyleProp, ViewStyle} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import type {OnyxEntry} from 'react-native-onyx';
import Avatar from '@components/Avatar';
import Checkbox from '@components/Checkbox';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
import iconWrapperStyle from '@components/Icon/IconWrapperStyles';
import {usePersonalDetails} from '@components/OnyxProvider';
import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback';
import RenderHTML from '@components/RenderHTML';
import {showContextMenuForReport} from '@components/ShowContextMenuContext';
import Text from '@components/Text';
import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails';
import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails';
import useLocalize from '@hooks/useLocalize';
Expand Down Expand Up @@ -54,9 +55,12 @@ type TaskPreviewProps = WithCurrentUserPersonalDetailsProps & {

/** Callback for updating context menu active state, used for showing context menu */
checkIfContextMenuActive: () => void;

/** Style for the task preview container */
style: StyleProp<ViewStyle>;
};

function TaskPreview({taskReportID, action, contextMenuAnchor, chatReportID, checkIfContextMenuActive, currentUserPersonalDetails, isHovered = false}: TaskPreviewProps) {
function TaskPreview({taskReportID, action, contextMenuAnchor, chatReportID, checkIfContextMenuActive, currentUserPersonalDetails, isHovered = false, style}: TaskPreviewProps) {
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const {translate} = useLocalize();
Expand All @@ -69,31 +73,35 @@ function TaskPreview({taskReportID, action, contextMenuAnchor, chatReportID, che
const isTaskCompleted = !isEmptyObject(taskReport)
? taskReport?.stateNum === CONST.REPORT.STATE_NUM.APPROVED && taskReport.statusNum === CONST.REPORT.STATUS_NUM.APPROVED
: action?.childStateNum === CONST.REPORT.STATE_NUM.APPROVED && action?.childStatusNum === CONST.REPORT.STATUS_NUM.APPROVED;
const taskTitle = Str.htmlEncode(TaskUtils.getTaskTitle(taskReportID, action?.childReportName ?? ''));
const taskTitle = Str.htmlEncode(TaskUtils.getTaskTitleFromReport(taskReport, action?.childReportName ?? ''));
const taskAssigneeAccountID = Task.getTaskAssigneeAccountID(taskReport) ?? action?.childManagerAccountID ?? -1;
const hasAssignee = taskAssigneeAccountID > 0;
const personalDetails = usePersonalDetails();
const avatar = personalDetails?.[taskAssigneeAccountID]?.avatar ?? Expensicons.FallbackAvatar;
const htmlForTaskPreview = `<comment>${taskTitle}</comment>`;
const avatarSize = CONST.AVATAR_SIZE.SMALL;
const isDeletedParentAction = ReportUtils.isCanceledTaskReport(taskReport, action);
const iconWrapperStyle = StyleUtils.getTaskPreviewIconWrapper(hasAssignee ? avatarSize : undefined);
const titleStyle = StyleUtils.getTaskPreviewTitleStyle(iconWrapperStyle.height, isTaskCompleted);

const shouldShowGreenDotIndicator = ReportUtils.isOpenTaskReport(taskReport, action) && ReportUtils.isReportManager(taskReport);
if (isDeletedParentAction) {
return <RenderHTML html={`<comment>${translate('parentReportAction.deletedTask')}</comment>`} />;
}

return (
<View style={[styles.chatItemMessage]}>
<View style={[styles.chatItemMessage, !hasAssignee && styles.mv1]}>
<PressableWithoutFeedback
onPress={() => Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(taskReportID))}
onPressIn={() => DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()}
onPressOut={() => ControlSelection.unblock()}
onLongPress={(event) => showContextMenuForReport(event, contextMenuAnchor, chatReportID, action, checkIfContextMenuActive)}
shouldUseHapticsOnLongPress
style={[styles.flexRow, styles.justifyContentBetween]}
style={[styles.flexRow, styles.justifyContentBetween, style]}
role={CONST.ROLE.BUTTON}
accessibilityLabel={translate('task.task')}
>
<View style={[styles.flex1, styles.flexRow, styles.alignItemsStart, styles.mt1]}>
<View style={[styles.taskCheckboxWrapper, styles.alignSelfCenter]}>
<View style={[styles.flex1, styles.flexRow, styles.alignItemsStart, styles.mr2]}>
<View style={iconWrapperStyle}>
<Checkbox
style={[styles.mr2]}
isChecked={isTaskCompleted}
Expand All @@ -108,21 +116,19 @@ function TaskPreview({taskReportID, action, contextMenuAnchor, chatReportID, che
accessibilityLabel={translate('task.task')}
/>
</View>
{taskAssigneeAccountID > 0 && (
{hasAssignee && (
<Avatar
containerStyles={[styles.mr2, styles.alignSelfCenter, isTaskCompleted ? styles.opacitySemiTransparent : undefined]}
containerStyles={[styles.mr2, isTaskCompleted ? styles.opacitySemiTransparent : undefined]}
source={avatar}
size={CONST.AVATAR_SIZE.SMALL}
size={avatarSize}
avatarID={taskAssigneeAccountID}
type={CONST.ICON_TYPE_AVATAR}
/>
)}
<View style={[styles.alignSelfCenter]}>
<RenderHTML html={isTaskCompleted ? `<completed-task>${htmlForTaskPreview}</completed-task>` : htmlForTaskPreview} />
</View>
<Text style={titleStyle}>{taskTitle}</Text>
</View>
{shouldShowGreenDotIndicator && (
<View style={[styles.ml2, iconWrapperStyle]}>
<View style={iconWrapperStyle}>
<Icon
src={Expensicons.DotIndicator}
fill={theme.success}
Expand All @@ -132,6 +138,7 @@ function TaskPreview({taskReportID, action, contextMenuAnchor, chatReportID, che
<Icon
src={Expensicons.ArrowRight}
fill={StyleUtils.getIconFillColor(getButtonState(isHovered))}
additionalStyles={iconWrapperStyle}
/>
</PressableWithoutFeedback>
</View>
Expand Down
13 changes: 9 additions & 4 deletions src/libs/TaskUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type {OnyxEntry} from 'react-native-onyx';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {Report} from '@src/types/onyx';
import type {Message} from '@src/types/onyx/ReportAction';
import type ReportAction from '@src/types/onyx/ReportAction';
import * as Localize from './Localize';
Expand Down Expand Up @@ -37,12 +38,16 @@ function getTaskReportActionMessage(action: OnyxEntry<ReportAction>): Pick<Messa
}
}

function getTaskTitle(taskReportID: string, fallbackTitle = ''): string {
const taskReport = ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`] ?? {};
function getTaskTitleFromReport(taskReport: OnyxEntry<Report>, fallbackTitle = ''): string {
// We need to check for reportID, not just reportName, because when a receiver opens the task for the first time,
// an optimistic report is created with the only property – reportName: 'Chat report',
// and it will be displayed as the task title without checking for reportID to be present.
return Object.hasOwn(taskReport, 'reportID') && 'reportName' in taskReport && typeof taskReport.reportName === 'string' ? taskReport.reportName : fallbackTitle;
return taskReport?.reportID && taskReport.reportName ? taskReport.reportName : fallbackTitle;
}

function getTaskTitle(taskReportID: string, fallbackTitle = ''): string {
const taskReport = ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`];
return getTaskTitleFromReport(taskReport, fallbackTitle);
}

function getTaskCreatedMessage(reportAction: OnyxEntry<ReportAction>) {
Expand All @@ -51,4 +56,4 @@ function getTaskCreatedMessage(reportAction: OnyxEntry<ReportAction>) {
return taskTitle ? Localize.translateLocal('task.messages.created', {title: taskTitle}) : '';
}

export {isActiveTaskEditRoute, getTaskReportActionMessage, getTaskTitle, getTaskCreatedMessage};
export {isActiveTaskEditRoute, getTaskReportActionMessage, getTaskTitle, getTaskTitleFromReport, getTaskCreatedMessage};
1 change: 1 addition & 0 deletions src/pages/home/report/ReportActionItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,7 @@ function ReportActionItem({
children = (
<ShowContextMenuContext.Provider value={contextValue}>
<TaskPreview
style={displayAsGroup ? [] : [styles.mt1]}
taskReportID={ReportActionsUtils.isAddCommentAction(action) ? ReportActionsUtils.getOriginalMessage(action)?.taskReportID?.toString() ?? '-1' : '-1'}
chatReportID={reportID}
action={action}
Expand Down
5 changes: 0 additions & 5 deletions src/styles/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4151,11 +4151,6 @@ const styles = (theme: ThemeColors) =>
width: 1,
},

taskCheckboxWrapper: {
height: variables.fontSizeNormalHeight,
...flex.justifyContentCenter,
},

taskTitleMenuItem: {
...writingDirection.ltr,
...headlineFont,
Expand Down
11 changes: 11 additions & 0 deletions src/styles/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1668,6 +1668,17 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({
alignItems: 'center',
justifyContent: 'center',
}),

getTaskPreviewIconWrapper: (avatarSize?: AvatarSizeName) => ({
height: avatarSize ? getAvatarSize(avatarSize) : variables.fontSizeNormalHeight,
...styles.justifyContentCenter,
}),

getTaskPreviewTitleStyle: (iconHeight: number, isTaskCompleted: boolean): StyleProp<TextStyle> => [
styles.flex1,
isTaskCompleted ? [styles.textSupporting, styles.textLineThrough] : [],
{marginTop: (iconHeight - variables.fontSizeNormalHeight) / 2},
],
});

type StyleUtilsType = ReturnType<typeof createStyleUtils>;
Expand Down

0 comments on commit fb4e24e

Please sign in to comment.