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
43 changes: 43 additions & 0 deletions examples/SampleApp/metro.config.no-dev.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* Metro config that forces a `dev=false` bundle for performance profiling.
*
* Use this to measure scroll/render perf WITHOUT React's dev-mode wrappers
* (`runWithFiberInDEV`, `getComponentStack`, etc — they account for ~22%
* of a captured profile and don't exist in release builds). Bundle is still
* unminified so function names stay readable in the .cpuprofile.
*
* Usage:
* yarn workspace sampleapp start --config metro.config.no-dev.js --reset-cache
*
* Then reload the app (shake → Reload, or `r` in Metro). The next bundle
* fetch will be served with dev=false regardless of what the app asks for.
* Run `node perf/capture-hermes-profile.js` as usual. To restore normal
* dev mode just stop Metro and start it again without `--config`.
*
* NOTE: this only changes the served JS bundle. The native binary is still
* a debug build; native code paths (Yoga, layout, view creation, image
* decoding) remain debug-instrumented. To benchmark a true release native
* pipeline you'd need to build a release variant of the app itself.
*/
const baseConfig = require('./metro.config.js');

module.exports = {
...baseConfig,
server: {
...(baseConfig.server || {}),
enhanceMiddleware: (middleware, metroServer) => {
const wrapped =
baseConfig.server && typeof baseConfig.server.enhanceMiddleware === 'function'
? baseConfig.server.enhanceMiddleware(middleware, metroServer)
: middleware;
return (req, res, next) => {
if (req.url && req.url.includes('dev=true')) {
req.url = req.url.replace(/([?&])dev=true/g, '$1dev=false');
// Print once-per-request so it's obvious what's happening.
process.stdout.write(`[no-dev] rewrote bundle URL to: ${req.url}\n`);
}
return wrapped(req, res, next);
};
},
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ type SwipableMessageWrapperProps = Pick<
onSwipe: () => void;
};

export const SwipableMessageWrapper = React.memo((props: SwipableMessageWrapperProps) => {
export const SwipableMessageWrapper = React.memo(function SwipableMessageWrapper(
props: SwipableMessageWrapperProps,
) {
const { children, messageSwipeToReplyHitSlop, onSwipe } = props;
const { MessageSwipeContent } = useComponentsContext();
const isRTL = I18nManager.isRTL;
Expand Down
68 changes: 32 additions & 36 deletions package/src/components/Message/MessageItemView/MessageContent.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useMemo } from 'react';
import { AnimatableNumericValue, ColorValue, Pressable, StyleSheet, View } from 'react-native';
import { ColorValue, Pressable, StyleSheet, View, ViewStyle } from 'react-native';

import { MessageTextContainer } from './MessageTextContainer';

Expand Down Expand Up @@ -169,47 +169,46 @@ const MessageContentWithContext = (props: MessageContentPropsWithContext) => {
[message, isMessageAIGenerated],
);

const getBorderRadius = () => {
// Merged background-color + border-radius object passed directly into the
// bubble's style array (no spread at the call site). Theme-defined radii
// override the group-position-computed defaults; theme-undefined radii are
// omitted so they don't override the computed defaults.
const bubbleColorAndRadius = useMemo<ViewStyle>(() => {
// enum('top', 'middle', 'bottom', 'single')
const groupPosition = groupStyles?.[0];

const isBottomOrSingle = groupPosition === 'single' || groupPosition === 'bottom';
let borderBottomLeftRadius = components.messageBubbleRadiusGroupBottom;
let borderBottomRightRadius = components.messageBubbleRadiusGroupBottom;

let computedBottomLeftRadius = components.messageBubbleRadiusGroupBottom;
let computedBottomRightRadius = components.messageBubbleRadiusGroupBottom;
if (isBottomOrSingle) {
// add relevant sharp corner
// add relevant sharp corner (the "tail")
if (isMyMessage) {
borderBottomRightRadius = components.messageBubbleRadiusTail;
computedBottomRightRadius = components.messageBubbleRadiusTail;
} else {
borderBottomLeftRadius = components.messageBubbleRadiusTail;
computedBottomLeftRadius = components.messageBubbleRadiusTail;
}
}

return {
borderBottomLeftRadius,
borderBottomRightRadius,
};
};

const getBorderRadiusFromTheme = () => {
const bordersFromTheme: Record<string, string | AnimatableNumericValue | undefined> = {
borderBottomLeftRadius,
borderBottomRightRadius,
borderRadius,
borderTopLeftRadius,
borderTopRightRadius,
const style: ViewStyle = {
backgroundColor,
borderBottomLeftRadius: borderBottomLeftRadius ?? computedBottomLeftRadius,
borderBottomRightRadius: borderBottomRightRadius ?? computedBottomRightRadius,
};
if (borderRadius !== undefined) style.borderRadius = borderRadius;
if (borderTopLeftRadius !== undefined) style.borderTopLeftRadius = borderTopLeftRadius;
if (borderTopRightRadius !== undefined) style.borderTopRightRadius = borderTopRightRadius;

// filter out undefined values
for (const key in bordersFromTheme) {
if (bordersFromTheme[key] === undefined) {
delete bordersFromTheme[key];
}
}

return bordersFromTheme;
};
return style;
}, [
backgroundColor,
borderBottomLeftRadius,
borderBottomRightRadius,
borderRadius,
borderTopLeftRadius,
borderTopRightRadius,
groupStyles,
isMyMessage,
]);

const { setNativeScrollability } = useMessageListItemContext();
const hasContentSideViews = !!(MessageContentLeadingView || MessageContentTrailingView);
Expand Down Expand Up @@ -357,12 +356,8 @@ const MessageContentWithContext = (props: MessageContentPropsWithContext) => {
<View
style={[
styles.containerInner,
{
backgroundColor,
...getBorderRadius(),
...getBorderRadiusFromTheme(),
},
noBorder ? { borderWidth: 0 } : {},
bubbleColorAndRadius,
noBorder ? styles.noBorder : null,
containerInner,
messageGroupedSingleOrBottom
? isVeryLastMessage && enableMessageGroupingByUser
Expand Down Expand Up @@ -684,6 +679,7 @@ const styles = StyleSheet.create({
alignSelf: 'center',
},
galleryContainer: {},
noBorder: { borderWidth: 0 },
rightAlignContent: {
justifyContent: 'flex-end',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export type MessageWrapperProps = {
nextMessage?: LocalMessage;
};

export const MessageWrapper = React.memo((props: MessageWrapperProps) => {
export const MessageWrapper = React.memo(function MessageWrapper(props: MessageWrapperProps) {
const { message, previousMessage, nextMessage } = props;
const { client } = useChatContext();
const {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,37 @@ const defaultMarkdownStyles: MarkdownStyle = {
fontSize: primitives.typographyFontSizeMd,
lineHeight: primitives.typographyLineHeightNormal,
},
// Heading sizes are derived from the body font size (`typographyFontSizeMd`) so they
// scale with the integrator's typography settings. lineHeight = fontSize × 1.25 to
// give headings room to breathe. Both fields are required here: without lineHeight,
// the inherited `lineHeight: typographyLineHeightNormal` (20) from `styles.text` (set
// in renderText below) leaks into the heading's inner Text via the markdown library's
// text rule (`{...styles.text, ...state.style}`) and squishes larger heading fontSizes
// into a 20px line box.
heading1: {
fontSize: primitives.typographyFontSizeMd * 2,
lineHeight: primitives.typographyFontSizeMd * 2 * 1.25,
},
heading2: {
fontSize: primitives.typographyFontSizeMd * 1.5,
lineHeight: primitives.typographyFontSizeMd * 1.5 * 1.25,
},
heading3: {
fontSize: primitives.typographyFontSizeMd * 1.25,
lineHeight: primitives.typographyFontSizeMd * 1.25 * 1.25,
},
heading4: {
fontSize: primitives.typographyFontSizeMd,
lineHeight: primitives.typographyFontSizeMd * 1.25,
},
heading5: {
fontSize: primitives.typographyFontSizeMd * 0.875,
lineHeight: primitives.typographyFontSizeMd * 0.875 * 1.25,
},
heading6: {
fontSize: primitives.typographyFontSizeMd * 0.75,
lineHeight: primitives.typographyFontSizeMd * 0.75 * 1.25,
},
inlineCode: {
padding: primitives.spacingXxs,
paddingHorizontal: primitives.spacingXxs,
Expand Down
17 changes: 11 additions & 6 deletions package/src/components/Message/hooks/useMessageDeliveryData.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { useCallback, useEffect, useState } from 'react';
import { useEffect, useRef, useState } from 'react';

import { Event, LocalMessage, UserResponse } from 'stream-chat';

import { useChannelContext } from '../../../contexts/channelContext/ChannelContext';
import { useChatContext } from '../../../contexts/chatContext/ChatContext';
import { useStableCallback } from '../../../hooks';

export const useMessageDeliveredData = ({ message }: { message?: LocalMessage }) => {
const { channel } = useChannelContext();
const { client } = useChatContext();
const calculate = useCallback(() => {

const messageIdRef = useRef<string>(message?.id);

const calculate = useStableCallback(() => {
if (!message?.created_at) {
return [];
}
Expand All @@ -17,13 +21,14 @@ export const useMessageDeliveredData = ({ message }: { message?: LocalMessage })
timestampMs: new Date(message.created_at).getTime(),
};
return channel.messageReceiptsTracker.deliveredForMessage(messageRef);
}, [channel, message]);
});

const [deliveredTo, setDeliveredTo] = useState<UserResponse[]>([]);
const [deliveredTo, setDeliveredTo] = useState<UserResponse[]>(() => calculate());

useEffect(() => {
if (!!messageIdRef.current && !!message?.id && messageIdRef.current !== message.id) {
setDeliveredTo(calculate());
}, [calculate]);
messageIdRef.current = message.id;
}

useEffect(() => {
const { unsubscribe } = channel.on('message.delivered', (event: Event) => {
Expand Down
17 changes: 11 additions & 6 deletions package/src/components/Message/hooks/useMessageReadData.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { useCallback, useEffect, useState } from 'react';
import { useEffect, useRef, useState } from 'react';

import { Event, LocalMessage, UserResponse } from 'stream-chat';

import { useChannelContext } from '../../../contexts/channelContext/ChannelContext';
import { useChatContext } from '../../../contexts/chatContext/ChatContext';
import { useStableCallback } from '../../../hooks';

export const useMessageReadData = ({ message }: { message?: LocalMessage }) => {
const { channel } = useChannelContext();
const { client } = useChatContext();
const calculate = useCallback(() => {

const messageIdRef = useRef<string>(message?.id);

const calculate = useStableCallback(() => {
if (!message?.created_at) {
return [];
}
Expand All @@ -18,13 +22,14 @@ export const useMessageReadData = ({ message }: { message?: LocalMessage }) => {
};

return channel.messageReceiptsTracker.readersForMessage(messageRef);
}, [channel, message]);
});

const [readBy, setReadBy] = useState<UserResponse[]>([]);
const [readBy, setReadBy] = useState<UserResponse[]>(() => calculate());

useEffect(() => {
if (!!messageIdRef.current && !!message?.id && messageIdRef.current !== message.id) {
setReadBy(calculate());
}, [calculate]);
messageIdRef.current = message.id;
}

useEffect(() => {
const { unsubscribe } = channel.on('message.read', (event: Event) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,7 @@ exports[`Thread should match thread snapshot 1`] = `
"borderBottomLeftRadius": 0,
"borderBottomRightRadius": 20,
},
{},
null,
{},
{},
]
Expand Down Expand Up @@ -843,7 +843,7 @@ exports[`Thread should match thread snapshot 1`] = `
"borderBottomLeftRadius": 0,
"borderBottomRightRadius": 20,
},
{},
null,
{},
{},
]
Expand Down Expand Up @@ -1208,7 +1208,7 @@ exports[`Thread should match thread snapshot 1`] = `
"borderBottomLeftRadius": 0,
"borderBottomRightRadius": 20,
},
{},
null,
{},
{},
]
Expand Down Expand Up @@ -1534,7 +1534,7 @@ exports[`Thread should match thread snapshot 1`] = `
"borderBottomLeftRadius": 0,
"borderBottomRightRadius": 20,
},
{},
null,
{},
{},
]
Expand Down
2 changes: 2 additions & 0 deletions perf/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
profiles/
*.cpuprofile
Loading
Loading