Skip to content
Open
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
6 changes: 5 additions & 1 deletion src/components/InlineSystemMessage.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import React from 'react';
import {View} from 'react-native';
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import {canUseTouchScreen} from '@libs/DeviceCapabilities';
import Icon from './Icon';
import Text from './Text';

Expand All @@ -14,7 +16,9 @@ type InlineSystemMessageProps = {
function InlineSystemMessage({message = ''}: InlineSystemMessageProps) {
const theme = useTheme();
const styles = useThemeStyles();
const {shouldUseNarrowLayout} = useResponsiveLayout();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MelvinBot, why are you using shouldUseNarrowLayout?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It mirrors the existing selectability pattern already used elsewhere in the chat, so behavior stays consistent across message types:

Both gate selectability on !canUseTouchScreen() || !shouldUseNarrowLayout. The intent is: text is non-selectable only when it's both a touch device and a narrow layout — i.e. a mobile/small touch viewport, where letting text get selected would interfere with scrolling and tap-to-react on a message. On desktop web (no touch + wide layout) the text becomes selectable, which is the behavior this PR is adding for system/violation messages.

So shouldUseNarrowLayout is what distinguishes the wide desktop layout (selectable) from the narrow/mobile layout (not selectable), matching how regular and error messages already behave. Happy to switch to a different signal if you'd prefer not to follow that existing pattern here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MelvinBot, what will be the behaviour on ipad? Also why aren't we only using !canUseTouchScreen()?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iPad behavior depends on layout width (these userSelect* styles are web-only — they have no effect in the native app, where Text selectability is governed by the selectable prop instead):

  • iPad web in landscape (window width > 800px → wide layout): !canUseTouchScreen() is false but !shouldUseNarrowLayout is true, so false || trueselectable.
  • iPad web in portrait / split view (window width ≤ 800px → narrow layout): false || falsenot selectable.

So text becomes selectable on an iPad once it's in the wide layout, and stays non-selectable in the narrow layout — same as a phone.

Why not only !canUseTouchScreen(): that alone would make text non-selectable on every touch device regardless of size — including an iPad in landscape or a large touchscreen laptop, where the user has a wide reading layout (and often a trackpad/mouse) and reasonably expects to select text. The || !shouldUseNarrowLayout adds those wide-layout touch devices back in.

Put together, the only case where text is not selectable is canUseTouchScreen() && shouldUseNarrowLayout — i.e. a touch device and a narrow layout (phones, iPad portrait/split). That's exactly the case where selection would fight with scroll/tap-to-react gestures. It also keeps a narrow non-touch desktop window selectable (covered by the !canUseTouchScreen() half), which using only !shouldUseNarrowLayout would not.

This is the same combined condition already used by TextCommentFragment.tsx:114 and DotIndicatorMessage.tsx:69, which is why I matched it here rather than dropping to !canUseTouchScreen() alone.

const icons = useMemoizedLazyExpensifyIcons(['Exclamation']);
const selectableStyle = !canUseTouchScreen() || !shouldUseNarrowLayout ? styles.userSelectText : styles.userSelectNone;
if (!message) {
return null;
}
Expand All @@ -25,7 +29,7 @@ function InlineSystemMessage({message = ''}: InlineSystemMessageProps) {
src={icons.Exclamation}
fill={theme.danger}
/>
<Text style={styles.inlineSystemMessage}>{message}</Text>
<Text style={[styles.inlineSystemMessage, selectableStyle]}>{message}</Text>
</View>
);
}
Expand Down
12 changes: 10 additions & 2 deletions src/components/ReportActionItem/TaskAction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import type {OnyxEntry} from 'react-native-onyx';
import RenderHTML from '@components/RenderHTML';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';
import {canUseTouchScreen} from '@libs/DeviceCapabilities';
import {getTaskReportActionMessage} from '@libs/TaskUtils';
import type {ReportAction} from '@src/types/onyx';

Expand All @@ -16,14 +18,20 @@ type TaskActionProps = {
function TaskAction({action}: TaskActionProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const {shouldUseNarrowLayout} = useResponsiveLayout();
const message = getTaskReportActionMessage(translate, action);
const isSelectable = !canUseTouchScreen() || !shouldUseNarrowLayout;
const selectableStyle = isSelectable ? styles.userSelectText : styles.userSelectNone;

return (
<View style={[styles.flex1, styles.flexRow, styles.alignItemsCenter, styles.breakWord, styles.preWrap]}>
{message.html ? (
<RenderHTML html={`<comment><muted-text>${message.html}</muted-text></comment>`} />
<RenderHTML
html={`<comment><muted-text>${message.html}</muted-text></comment>`}
isSelectable={isSelectable}
/>
) : (
<Text style={[styles.chatItemMessage, styles.colorMuted]}>{message.text}</Text>
<Text style={[styles.chatItemMessage, styles.colorMuted, selectableStyle]}>{message.text}</Text>
)}
</View>
);
Expand Down
8 changes: 6 additions & 2 deletions src/pages/inbox/report/ReportActionItemBasicMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import {Str} from 'expensify-common';
import React, {useMemo} from 'react';
import {View} from 'react-native';
import Text from '@components/Text';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';
import {canUseTouchScreen} from '@libs/DeviceCapabilities';
import {containsCustomEmoji, containsOnlyCustomEmoji} from '@libs/EmojiUtils';
import type ChildrenProps from '@src/types/utils/ChildrenProps';
import TextWithEmojiFragment from './comment/TextWithEmojiFragment';
Expand All @@ -13,19 +15,21 @@ type ReportActionItemBasicMessageProps = Partial<ChildrenProps> & {

function ReportActionItemBasicMessage({message, children}: ReportActionItemBasicMessageProps) {
const styles = useThemeStyles();
const {shouldUseNarrowLayout} = useResponsiveLayout();
const messageContainsCustomEmojiWithText = useMemo(() => containsCustomEmoji(message) && !containsOnlyCustomEmoji(message), [message]);
const selectableStyle = !canUseTouchScreen() || !shouldUseNarrowLayout ? styles.userSelectText : styles.userSelectNone;

return (
<View>
{!!message &&
(messageContainsCustomEmojiWithText ? (
<TextWithEmojiFragment
message={Str.htmlDecode(message)}
style={[styles.chatItemMessage, styles.colorMuted]}
style={[styles.chatItemMessage, styles.colorMuted, selectableStyle]}
alignCustomEmoji
/>
) : (
<Text style={[styles.chatItemMessage, styles.colorMuted]}>{Str.htmlDecode(message)}</Text>
<Text style={[styles.chatItemMessage, styles.colorMuted, selectableStyle]}>{Str.htmlDecode(message)}</Text>
))}
{children}
</View>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import useDelegateAccountID from '@hooks/useDelegateAccountID';
import useEnvironment from '@hooks/useEnvironment';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import {openLink} from '@libs/actions/Link';
import {explain} from '@libs/actions/Report';
import {canUseTouchScreen} from '@libs/DeviceCapabilities';
import {hasReasoning} from '@libs/ReportActionsUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
Expand Down Expand Up @@ -38,6 +40,7 @@ function ReportActionItemMessageWithExplain({message, action, childReport, origi
const {translate} = useLocalize();
const personalDetail = useCurrentUserPersonalDetails();
const {environmentURL} = useEnvironment();
const {shouldUseNarrowLayout} = useResponsiveLayout();
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
const [isSelfTourViewed] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasSeenTourSelector});
const [betas] = useOnyx(ONYXKEYS.BETAS);
Expand All @@ -61,7 +64,7 @@ function ReportActionItemMessageWithExplain({message, action, childReport, origi
<ReportActionItemBasicMessage>
<RenderHTML
html={`<comment><muted-text>${computedMessage}</muted-text></comment>`}
isSelectable={false}
isSelectable={!canUseTouchScreen() || !shouldUseNarrowLayout}
onLinkPress={handleLinkPress}
/>
</ReportActionItemBasicMessage>
Expand Down
Loading