Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[HOLD for payment 2024-05-15] HIGH: [Polish] [$500] Redesign thread ancestry #36752

Closed
quinthar opened this issue Feb 17, 2024 · 114 comments
Closed
Assignees
Labels
Awaiting Payment Auto-added when associated PR is deployed to production Daily KSv2 Engineering External Added to denote the issue can be worked on by a contributor NewFeature Something to build that is a new item.

Comments

@quinthar
Copy link
Contributor

quinthar commented Feb 17, 2024

Problem:

Infinite threading means you can get buried deep in a hierarchy and lose track of where you are. We show your ancestors to help with this, but user complaints suggest this isn't enough:

Image

Solution:

Make it look more clear! There was a log discussion on this (Slack) ending with this design:

Image

Specifically:

  • Add a blue "thread" link to each separator line
  • Add a gray "replies" message to the final separator
  • From below - we want the entire message (including the blue thread icon) to be clickable to navigate you to the thread.
  • From below - clicking on an ancestor message should take you to the thread/room where the message originally appeared, not the thread for the message.
Upwork Automation - Do Not Edit
  • Upwork Job URL: https://www.upwork.com/jobs/~01c49e505032aeb5f6
  • Upwork Job ID: 1758664864087191552
  • Last Price Increase: 2024-03-16
  • Automatic offers:
    • aimane-chnaif | Reviewer | 0
    • rayane-djouah | Contributor | 0
@quinthar quinthar added External Added to denote the issue can be worked on by a contributor Engineering Daily KSv2 labels Feb 17, 2024
Copy link

melvin-bot bot commented Feb 17, 2024

Auto-assigning issues to engineers is no longer supported. If you think this issue should receive engineering attention, please raise it in #whatsnext.

@melvin-bot melvin-bot bot changed the title HIGH: Redesign thread ancestry [$500] HIGH: Redesign thread ancestry Feb 17, 2024
Copy link

melvin-bot bot commented Feb 17, 2024

Job added to Upwork: https://www.upwork.com/jobs/~01c49e505032aeb5f6

@melvin-bot melvin-bot bot added the Help Wanted Apply this label when an issue is open to proposals by contributors label Feb 17, 2024
Copy link

melvin-bot bot commented Feb 17, 2024

Triggered auto assignment to Contributor-plus team member for initial proposal review - @aimane-chnaif (External)

@neonbhai
Copy link
Contributor

Proposal

Please re-state the problem that we are trying to solve in this issue.

Redesign thread ancestr

What is the root cause of that problem?

New Feature

What changes do you think we should make in order to solve the problem?

We will be modifying the thread divider line here in regards to its ancestors:

{!ancestor.shouldHideThreadDividerLine && <View style={[styles.threadDividerLine]} />}

We will:

  • Wrap Text and Icon and Divider line with a view like this:

    <View style={[styles.flexRow, styles.alignItemsCenter, styles.ml5]}>
        <Icon />
        <Text style={[styles.link}>Thread</Text> 
        <View style={[styles.threadDividerLine]} />
    </View>
  • We can put this in a component called ThreadDividerLine

  • Render a divider line before elements here with text Thread

  • Take in index as the second argument when mapping ancestors here:

    {allAncestors.map((ancestor) => (

  • Render a divider line here according to the logic checking for last element:

    {i !== allAncestors.length - 1 ? <ThreadDividerLine text='common.Thread' fill={colors.blue400}/> : <ThreadDividerLine text='common.Replies'/>
Result

Icon will have to be requested from the design team

Screenshot 2024-02-17 at 8 28 02 AM

@rayane-djouah
Copy link
Contributor

rayane-djouah commented Feb 17, 2024

Proposal

Please re-state the problem that we are trying to solve in this issue.

Redesign thread ancestry

What is the root cause of that problem?

New Feature

What changes do you think we should make in order to solve the problem?

  1. Add a blue "thread" link to each separator line

to do this, we should remove the current thread separator that is displayed below every ancestors here:

<ReportActionItem
// @ts-expect-error TODO: Remove this once ReportActionItem (https://github.com/Expensify/App/issues/31982) is migrated to TypeScript.
onPress={() => Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(ancestor.report.reportID))}
report={ancestor.report}
action={ancestor.reportAction}
displayAsGroup={false}
isMostRecentIOUReportAction={false}
shouldDisplayNewMarker={ancestor.shouldDisplayNewMarker}
index={index}
/>
{!ancestor.shouldHideThreadDividerLine && <View style={[styles.threadDividerLine]} />}

and add a blue "thread" link with a thread divider line above every ancestor ReportActionItem here, we should add this code:

<View style={[styles.flexRow, styles.alignItemsCenter, styles.ml5, index === 0 ? styles.mv2 : [styles.mt3, styles.mb1]]}>
    <PressableWithoutFeedback
        onPress={() =>  Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(ancestor?.report?.parentReportID ?? ''))}
        accessibilityLabel={"Thread"}
        role={CONST.ROLE.BUTTON}
        style={[styles.flexRow, styles.alignItemsCenter, styles.gap1]}
    >
        <Icon
            src={Expensicons.Thread}
            fill={theme.link}
            width={variables.iconSizeExtraSmall}
            height={variables.iconSizeExtraSmall}
        />
        <Text style={[styles.threadDividerText, styles.link]}>Thread</Text> 
    </PressableWithoutFeedback>
    {!ancestor.shouldDisplayNewMarker && <View style={[styles.threadDividerLine]} />} 
</View>
  • Thread's divider line should hide when the ancestor item is marked as unread, This is so that they will not be conflicting.
    we will determine if we should display the Thread's divider line with ancestor.shouldDisplayNewMarker, therefore, the ancestor.shouldHideThreadDividerLine will not be needed anymore and we should remove it, here, and here.

From #36752 (comment) - we want the entire message (including the blue thread icon) to be clickable to navigate you to the thread.
From #36752 (comment) - clicking on an ancestor message should take you to the thread/room where the message originally appeared, not the thread for the message.

  • we should use PressableWithoutFeedback to make the link clickable.
  • we should pass Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(ancestor.report.reportID)) to the PressableWithoutFeedback onPress prop to navigate to the thread on click.
  • we should change the ReportActionItem onPress prop here to:
onPress={() =>  Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(ancestor?.report?.parentReportID ?? ''))}
  • we should use theme.link and styles.link to make the icon and "Thread" text blue.
  • For "Thread" and "Replies" text we should use this style:
threadDividerText: {
    fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
    fontSize: variables.fontSizeSmall,
    textTransform: 'capitalize',
},
  • we should change the threadDividerLine margins to match the design, instead of marginHorizontal: 20, we should use marginLeft: 8, marginRight: 20,
  • we should use ancestor index to apply a 8px vertical margin for the first ancestor, and for others we apply 12px top margin and 4px bottom margin.
  • if we want to display a pointer cursor on the report message, we need to modify the getReportActionItemStyle function here to return styles.cursorPointer in the styles based on a boolean, and we should pass this boolean here as !_.isUndefined(props.onPress) and remove the cursor.cursorAuto style for the message here
  1. Add a gray "replies" message to the final separator

to do this, after displaying all ancestors here, we should add below them this code:

<View style={[styles.flexRow, styles.alignItemsCenter, styles.ml5, styles.mv1]}>
    <Icon
        src={Expensicons.Thread}
        fill={theme.icon}
        width={variables.iconSizeExtraSmall}
        height={variables.iconSizeExtraSmall}
    />
    <Text style={[styles.threadDividerText, styles.textSupporting, styles.ml1]}>Replies</Text> 
    {!shouldHideThreadDividerLine && <View style={[styles.threadDividerLine]} />} 
</View>
  • Thread's divider line should hide when the first item in the current thread is marked as unread, This is so that they will not be conflicting.
  • we should use theme.icon and styles.textSupporting to make the icon and "Replies" text gray.

We should use localization function for "Thread" and "Replies" messages.

We can refactor the code into reusable components.

Compare changes

Branch link for testing

Result

Threads.Redesign.Recording.2024-03-05.174204.mp4

What alternative solutions did you explore? (Optional)

N/A

@dannymcclain
Copy link
Contributor

Figma file here. cc'ing @Expensify/design on this for visibility.

And here's one more mockup to show more context around how ancestry works:
image

This look right to you @designteam?

@melvin-bot melvin-bot bot added the Overdue label Feb 19, 2024
@shawnborton
Copy link
Contributor

That looks right to me, thanks for laying it out like that!

@mallenexpensify mallenexpensify added the NewFeature Something to build that is a new item. label Feb 20, 2024
Copy link

melvin-bot bot commented Feb 20, 2024

@melvin-bot melvin-bot bot added Weekly KSv2 and removed Overdue Daily KSv2 labels Feb 20, 2024
@mallenexpensify
Copy link
Contributor

@aimane-chnaif can you please review the proposals above?
Added NewFeature to assign a BZ to help move this forward. 👋 @dylanexpensify .

@dylanexpensify
Copy link
Contributor

Nice nice, thanks Matt!

@quinthar
Copy link
Contributor Author

@aimane-chnaif what's the holdup -- what are the next steps and when will they get done?

@aimane-chnaif
Copy link
Contributor

Ah this is weekly so missed from my radar. 2 proposals to review. will update on Monday

Copy link

melvin-bot bot commented Feb 24, 2024

📣 It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? 💸

@mallenexpensify mallenexpensify added Daily KSv2 and removed Weekly KSv2 labels Feb 26, 2024
@mallenexpensify
Copy link
Contributor

Ah this is weekly so missed from my radar. 2 proposals to review. will update on Monday

New Features default to Weekly, I didn't notice that either. I bumped to Daily.

@Victor-Nyagudi
Copy link
Contributor

Victor-Nyagudi commented Feb 27, 2024

Proposal

Please re-state the problem that we are trying to solve in this issue.

Redesign thread ancestry.

What is the root cause of that problem?

No problem here. This is a new feature.

What changes do you think we should make in order to solve the problem?

The new thread and replies markers look very similar to the unread message indicator, so I propose refactoring the UnreadActionIndicator component and re-using some of the logic already present there, that way, if we ever need to make changes to any markers, we can do so in one place.

It also helps with organization and debugging when all related logic is kept together.

Here's how we'll go about this.

  1. Given the current UnreadActionIndicator component will now also act as a thread marker, we'll give it a new, more generic name like MessageDivider.

  2. We'll get the 'comment bubble reply' icon from Figma and add it to the Images folder i.e. root/assets/images.

This icon will be imported in Expensicons.ts and exported accordingly.

  1. The now MessageDivider will need three new props: isThreadDividerLine, isLastThread, and parentReportID.
type UnreadActionIndicatorProps = {
    reportActionID: string;
+    isThreadDividerLine?: boolean;
+    isLastThread?: boolean;
+.   parentReportID?: string | undefined
};

These will be false or an empty string by default.

function UnreadActionIndicator({reportActionID, parentReportID = '', isThreadDividerLine = false, isLastThread = false}: UnreadActionIndicatorProps)
  1. We'll style the current divider line in UnreadActionIndicator according to whether it's for an unread marker or a thread marker.
<View style={isThreadDividerLine ? styles.threadDividerLine : styles.unreadIndicatorLine} />
  1. Next, we'll conditionally render a thread marker or a replies marker based on whether the current thread is the last one.

This marker will be stored in a variable below the current two variables already present.

const threadDividerChildren = (
    <>
        <Icon
            src={Expensicons.CommentBubbleReply}
            fill={isLastThread ? styles.textSupporting.color : styles.link.color}
            width={variables.iconSizeExtraSmall}
            height={variables.iconSizeExtraSmall}
            additionalStyles={styles.mr1}
        />

       <Text style={[styles.unreadIndicatorText, isLastThread ? styles.textSupporting : styles.link]}>{isLastThread ? 'Replies' : 'Thread'}</Text>
    </>
);

const threadDivider = isLastThread ? (
    <View style={[styles.flexRow, styles.alignItemsCenter]}>{threadDividerChildren}</View>
) : (
    <PressableWithoutFeedback
        onPress={() => {
            Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(parentReportID));
        }}
        // Dummy label for now
        accessibilityLabel={'Thread marker link'}
        role={CONST.ROLE.LINK}
        style={[styles.flexRow, styles.alignItemsCenter]}
    >
        {threadDividerChildren}
    </PressableWithoutFeedback>
);
  1. One of the benefits of re-using UnreadActionIndicator component is we won't need to repeat the logic preventing its content from being selected or copied to the clipboard (introduced in this PR to prevent copying content from the mini context menu and established a pattern used in multiple places in the codebase).

This is what the new code in the return statement will look like.

<View
    // This will require a more generic label like 'MessageLineDivider'
    accessibilityLabel={translate('accessibilityHints.newMessageLineIndicator')}
    data-action-id={reportActionID}
    style={[isThreadDividerLine ? styles.threadDividerLineContainer : styles.unreadIndicatorContainer, styles.userSelectNone, !isThreadDividerLine && styles.pointerEventsNone]}
    dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}}
>
    <View style={isThreadDividerLine ? styles.threadDividerLine : styles.unreadIndicatorLine} />

    {isThreadDividerLine ? threadDivider : <Text style={styles.unreadIndicatorText}>{translate('common.new')}</Text>}
</View>
  1. Over in ReportActionItemParentAction, we'll render the now MessageDivider just before ReportActionItem and pass ancestor.reportAction.reportActionID to UnreadActionIndicator's reportActionID prop.

isLastThread will be false thus the marker rendered will be the one containing the blue icon and the word 'Thread'.

isThreadDividerLine will be true so a thread marker is rendered instead of an unread marker, and we'll pass ancestor.report.parentReportID to parentReportID.

The ReportActionItem will also use the ancestor.report.parentReportID instead of ancestor.report.reportID.

Similarly, we'll conditionally render the now MessageDivider after ReportActionItem, but this time, only if it's the last thread i.e. it's the last ancestor in the allAncestors array.

Since we're already mapping through this array, we can use the index as i in the map function to check if we're on the last item in the array.

We'll also pass the outcome of this check to isLastThread so that when it's true, the replies marker is rendered. We could also explicitly pass true to the prop or store the boolean in a variable somewhere - either works.

Here's what the code from line 78 in ReportActionItemParentAction will now look like.

+ {allAncestors.map((ancestor, i) => (
    <OfflineWithFeedback
        // ... props
    >
+       <UnreadActionIndicator
+           reportActionID={ancestor.reportAction.reportActionID}
+           isThreadDividerLine={true}
+           parentReportID={ancestor.report.parentReportID} 
+       />
             <ReportActionItem
-               onPress={() => Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(ancestor.report.reportID))}
+               onPress={() => Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(ancestor.report.parentReportID))}
                // ... props
             />
+          {!shouldHideThreadDividerLine && i === allAncestors.length - 1 && (
+              <UnreadActionIndicator
+                  reportActionID={ancestor.report.reportID}
+                  isThreadDividerLine={true}
+                  isLastThread={i === allAncestors.length - 1}
+              />
            )}
  1. Lastly, the container housing the thread divider line will have the following styles (added to index.ts in the styles folder).
threadDividerLineContainer: {
    flexDirection: 'row-reverse',
    alignItems: 'center',
    width: '100%',
    paddingHorizontal: 20,
    paddingVertical: 4
},

The thread divider line will have a marginLeft: 8 instead of marginHorizontal: 20 to align with what's in the design file.

threadDividerLine: {
    height: 1,
    backgroundColor: theme.border,
    flexGrow: 1,
-  marginHorizontal: 20,            
+  marginLeft: 8,
},

The words 'Replies' and 'Thread' will be translated using the translate() method once the translations have been verified along with any other copy required.

Short demo after changes
expensify-updated-thread-redesign-solution.mov

What alternative solutions did you explore? (Optional)

None as of yet.

@Victor-Nyagudi
Copy link
Contributor

Victor-Nyagudi commented Feb 27, 2024

I apologize in advance for the lack of proper formatting on the code blocks.

My computer is at 3%, and I'm nowhere near a charging source. I intend to format them correctly once I've recharged my device.

I see the video is uploaded, but it's taking a while to load/play for me. Let me know if you can see it, or if I need to upload it again.

All good now.

@melvin-bot melvin-bot bot removed the Overdue label Apr 29, 2024
@dylanexpensify
Copy link
Contributor

Similar

@melvin-bot melvin-bot bot added the Overdue label May 3, 2024
@dannymcclain
Copy link
Contributor

Looks like 39982 has been deployed.

@melvin-bot melvin-bot bot removed the Overdue label May 6, 2024
@rayane-djouah
Copy link
Contributor

rayane-djouah commented May 6, 2024

Off hold, The PR is merged

@dylanexpensify
Copy link
Contributor

NOICE!

@dylanexpensify dylanexpensify changed the title [HELD #39982][HOLD for payment 2024-04-09] HIGH: [Polish] [$500] Redesign thread ancestry [HOLD for payment 2024-04-09] HIGH: [Polish] [$500] Redesign thread ancestry May 7, 2024
@dylanexpensify
Copy link
Contributor

PR not yet on staging, waiting

@dylanexpensify
Copy link
Contributor

Payment summary:

Please apply/request!

@melvin-bot melvin-bot bot added the Overdue label May 9, 2024
Copy link

melvin-bot bot commented May 10, 2024

@dannymcclain, @chiragsalian, @dylanexpensify, @rayane-djouah Uh oh! This issue is overdue by 2 days. Don't forget to update your issues!

@rayane-djouah
Copy link
Contributor

PR deployed on May 9

@melvin-bot melvin-bot bot removed the Overdue label May 13, 2024
@dylanexpensify dylanexpensify changed the title [HOLD for payment 2024-04-09] HIGH: [Polish] [$500] Redesign thread ancestry [HOLD for payment 2024-05-15] HIGH: [Polish] [$500] Redesign thread ancestry May 13, 2024
@dylanexpensify
Copy link
Contributor

updated to reflect

@dylanexpensify
Copy link
Contributor

payment tomorrow!

@rayane-djouah
Copy link
Contributor

Please correct the payment summary, the payment amount is 500$ not 750$

@melvin-bot melvin-bot bot added the Overdue label May 17, 2024
Copy link

melvin-bot bot commented May 20, 2024

@dannymcclain, @chiragsalian, @dylanexpensify, @rayane-djouah Eep! 4 days overdue now. Issues have feelings too...

@dylanexpensify
Copy link
Contributor

@rayane-djouah we decided $750 given there was a follow on issue created and done that wasn't necessarily needed to solve the bug, but a polish. Does that sound right? cc @chiragsalian

@melvin-bot melvin-bot bot removed the Overdue label May 21, 2024
@rayane-djouah
Copy link
Contributor

@dylanexpensify I trust your judgment on what you find fair. Thanks!

@melvin-bot melvin-bot bot added the Overdue label May 27, 2024
@dannymcclain
Copy link
Contributor

Not overdue, right @dylanexpensify?

@melvin-bot melvin-bot bot removed the Overdue label May 27, 2024
@dylanexpensify
Copy link
Contributor

Nope! Just finishing payments!

@dylanexpensify
Copy link
Contributor

Done!

@JmillsExpensify
Copy link

$750 approved for @allroundexperts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting Payment Auto-added when associated PR is deployed to production Daily KSv2 Engineering External Added to denote the issue can be worked on by a contributor NewFeature Something to build that is a new item.
Projects
No open projects
Development

No branches or pull requests