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
72 changes: 72 additions & 0 deletions package/src/components/ChannelPreview/ChannelMessagePreview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import React, { useMemo } from 'react';
import { View, Text, StyleSheet } from 'react-native';

import { DraftMessage, LocalMessage, MessageResponse } from 'stream-chat';

import { useTheme } from '../../contexts/themeContext/ThemeContext';
import { useMessagePreviewIcon, useMessagePreviewText } from '../../hooks';
import { primitives } from '../../theme';

export type ChannelMessagePreviewProps = {
message: LocalMessage | MessageResponse | DraftMessage;
};

export const ChannelMessagePreview = ({ message }: ChannelMessagePreviewProps) => {
const isMessageDeleted = message?.type === 'deleted';
const {
theme: { semantics },
} = useTheme();
const styles = useStyles({ isMessageDeleted });
const MessagePreviewIcon = useMessagePreviewIcon({ message });
const messagePreviewTitle = useMessagePreviewText({ message });

return (
<View style={[styles.container]}>
{MessagePreviewIcon ? (
<MessagePreviewIcon
height={16}
stroke={isMessageDeleted ? semantics.textTertiary : semantics.textSecondary}
width={16}
/>
) : null}
<Text numberOfLines={1} style={[styles.subtitle]}>
{messagePreviewTitle}
</Text>
</View>
);
};

const useStyles = ({ isMessageDeleted = false }: { isMessageDeleted?: boolean }) => {
const {
theme: {
channelPreview: { messagePreview },
semantics,
},
} = useTheme();
return useMemo(() => {
return StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
gap: primitives.spacingXxs,
flexShrink: 1,
...messagePreview.container,
},
subtitle: {
color: isMessageDeleted ? semantics.textTertiary : semantics.textSecondary,
fontSize: primitives.typographyFontSizeSm,
fontWeight: primitives.typographyFontWeightRegular,
includeFontPadding: false,
lineHeight: primitives.typographyLineHeightNormal,
flexShrink: 1,
...messagePreview.subtitle,
},
});
}, [
isMessageDeleted,
semantics.textTertiary,
semantics.textSecondary,
messagePreview.container,
messagePreview.subtitle,
]);
};
Original file line number Diff line number Diff line change
@@ -1,28 +1,26 @@
import React, { useMemo } from 'react';
import { StyleSheet, Text, View } from 'react-native';

import { LocalMessage } from 'stream-chat';
import { LocalMessage, MessageResponse } from 'stream-chat';

import { ChannelPreviewProps } from './ChannelPreview';
import { LastMessageType } from './hooks/useChannelPreviewData';

import { MessageDeliveryStatus, useMessageDeliveryStatus } from './hooks/useMessageDeliveryStatus';

import { useChatContext } from '../../contexts/chatContext/ChatContext';
import { useTheme } from '../../contexts/themeContext/ThemeContext';
import { useTranslationContext } from '../../contexts/translationContext/TranslationContext';
import { MessageDeliveryStatus, useMessageDeliveryStatus } from '../../hooks';
import { Check, CheckAll, Time } from '../../icons';
import { primitives } from '../../theme';
import { MessageStatusTypes } from '../../utils/utils';

export type ChannelListMessageDeliveryStatusProps = Pick<ChannelPreviewProps, 'channel'> & {
lastMessage: LastMessageType;
export type ChannelMessagePreviewDeliveryStatusProps = Pick<ChannelPreviewProps, 'channel'> & {
message: MessageResponse | LocalMessage;
};

export const ChannelListMessageDeliveryStatus = ({
export const ChannelMessagePreviewDeliveryStatus = ({
channel,
lastMessage,
}: ChannelListMessageDeliveryStatusProps) => {
message,
}: ChannelMessagePreviewDeliveryStatusProps) => {
const { client } = useChatContext();
const { t } = useTranslationContext();
const channelConfigExists = typeof channel?.getConfig === 'function';
Expand All @@ -36,9 +34,15 @@ export const ChannelListMessageDeliveryStatus = ({
},
} = useTheme();

const membersWithoutSelf = useMemo(() => {
return Object.values(channel.state?.members || {}).filter(
(member) => member.user?.id !== client.user?.id,
);
}, [channel.state?.members, client.user?.id]);

const isLastMessageByCurrentUser = useMemo(() => {
return lastMessage?.user?.id === client.user?.id;
}, [lastMessage, client.user?.id]);
return message?.user?.id === client.user?.id;
}, [message, client.user?.id]);

const readEvents = useMemo(() => {
if (!channelConfigExists) {
Expand All @@ -53,19 +57,23 @@ export const ChannelListMessageDeliveryStatus = ({

const { status } = useMessageDeliveryStatus({
channel,
lastMessage: lastMessage as LocalMessage,
lastMessage: message as LocalMessage,
isReadEventsEnabled: readEvents,
});

if (!isLastMessageByCurrentUser) {
if (!channel.data?.name && membersWithoutSelf.length === 1 && !isLastMessageByCurrentUser) {
return null;
}

if (!isLastMessageByCurrentUser) {
return <Text style={styles.username}>{message?.user?.name || message?.user?.id}:</Text>;
}

return (
<View style={styles.container}>
{lastMessage.status === MessageStatusTypes.SENDING ? (
{message.status === MessageStatusTypes.SENDING ? (
<Time stroke={semantics.chatTextTimestamp} height={16} width={16} {...timeIcon} />
) : lastMessage.status === MessageStatusTypes.RECEIVED &&
) : message.status === MessageStatusTypes.RECEIVED &&
status === MessageDeliveryStatus.READ ? (
<CheckAll stroke={semantics.accentPrimary} height={16} width={16} {...checkAllIcon} />
) : status === MessageDeliveryStatus.DELIVERED ? (
Expand Down Expand Up @@ -103,6 +111,12 @@ const useStyles = () => {
lineHeight: primitives.typographyLineHeightNormal,
...text,
},
username: {
color: semantics.textTertiary,
fontSize: primitives.typographyFontSizeSm,
fontWeight: primitives.typographyFontWeightSemiBold,
lineHeight: primitives.typographyLineHeightNormal,
},
});
}, [semantics, text, container]);
};
37 changes: 8 additions & 29 deletions package/src/components/ChannelPreview/ChannelPreviewMessage.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React, { useCallback, useMemo } from 'react';
import React, { useMemo } from 'react';
import { StyleSheet, Text, View } from 'react-native';

import { DraftMessage, LocalMessage, MessageResponse } from 'stream-chat';

import { ChannelMessagePreview } from './ChannelMessagePreview';
import { ChannelMessagePreviewDeliveryStatus } from './ChannelMessagePreviewDeliveryStatus';
import { ChannelPreviewProps } from './ChannelPreview';

import { ChannelTypingIndicatorPreview } from './ChannelTypingIndicatorPreview';
Expand All @@ -20,8 +20,6 @@ import { useTranslationContext } from '../../contexts/translationContext/Transla
import { NewPoll } from '../../icons/NewPoll';
import { primitives } from '../../theme';
import { MessageStatusTypes } from '../../utils/utils';
import { MessagePreview } from '../MessagePreview/MessagePreview';
import { MessagePreviewUserDetails } from '../MessagePreview/MessagePreviewUserDetails';
import { ErrorBadge } from '../ui';

export type ChannelPreviewMessageProps = Pick<ChannelPreviewProps, 'channel'> & {
Expand Down Expand Up @@ -53,25 +51,6 @@ export const ChannelPreviewMessage = (props: ChannelPreviewMessageProps) => {
const isFailedMessage =
lastMessage?.status === MessageStatusTypes.FAILED || lastMessage?.type === 'error';

const textStyle = useMemo(() => {
return [styles.subtitle];
}, [styles.subtitle]);

const iconProps = useMemo(() => {
return {
width: 16,
height: 16,
stroke: isMessageDeleted ? semantics.textTertiary : semantics.textSecondary,
};
}, [isMessageDeleted, semantics.textTertiary, semantics.textSecondary]);

const renderMessagePreview = useCallback(
(message: LocalMessage | MessageResponse | DraftMessage) => {
return <MessagePreview message={message} textStyle={textStyle} iconProps={iconProps} />;
},
[textStyle, iconProps],
);

if (usersTyping.length > 0) {
return <ChannelTypingIndicatorPreview channel={channel} usersTyping={usersTyping} />;
}
Expand All @@ -80,7 +59,7 @@ export const ChannelPreviewMessage = (props: ChannelPreviewMessageProps) => {
return (
<View style={styles.container}>
<Text style={styles.draftText}>{t('Draft')}:</Text>
{renderMessagePreview(draftMessage)}
<ChannelMessagePreview message={draftMessage} />
</View>
);
}
Expand Down Expand Up @@ -115,15 +94,15 @@ export const ChannelPreviewMessage = (props: ChannelPreviewMessageProps) => {
if (channel.data?.name || membersWithoutSelf.length > 1) {
return (
<View style={styles.container}>
<MessagePreviewUserDetails channel={channel} message={lastMessage} />
{renderMessagePreview(lastMessage)}
<ChannelMessagePreviewDeliveryStatus channel={channel} message={lastMessage} />
<ChannelMessagePreview message={lastMessage} />
</View>
);
} else {
return (
<View style={styles.container}>
<MessagePreviewUserDetails channel={channel} message={lastMessage} />
{renderMessagePreview(lastMessage)}
<ChannelMessagePreviewDeliveryStatus channel={channel} message={lastMessage} />
<ChannelMessagePreview message={lastMessage} />
</View>
);
}
Expand Down
111 changes: 57 additions & 54 deletions package/src/components/Indicators/EmptyStateIndicator.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React from 'react';
import React, { useMemo } from 'react';
import { StyleSheet, Text, View } from 'react-native';

import { useTheme } from '../../contexts/themeContext/ThemeContext';
import { useTranslationContext } from '../../contexts/translationContext/TranslationContext';
import { useViewport } from '../../hooks/useViewport';
import { ChatIcon, MessageBubbleEmpty, MessageIcon } from '../../icons';
import { MessageBubbleEmpty } from '../../icons';
import { primitives } from '../../theme';

export type EmptyStateProps = {
listType?: 'channel' | 'message' | 'threads' | 'default';
Expand All @@ -13,78 +13,81 @@ export type EmptyStateProps = {
export const EmptyStateIndicator = ({ listType }: EmptyStateProps) => {
const {
theme: {
colors: { black, grey, grey_gainsboro },
emptyStateIndicator: {
channelContainer,
channelDetails,
channelTitle,
messageContainer,
messageTitle,
},
emptyStateIndicator: { channelContainer, channelTitle, messageContainer, messageTitle },
semantics,
},
} = useTheme();
const { vw } = useViewport();
const width = vw(33);
const { t } = useTranslationContext();
const styles = useStyles();

switch (listType) {
case 'channel':
return (
<View style={[styles.container, channelContainer]}>
<MessageIcon height={width} pathFill={grey_gainsboro} width={width} />
<Text
style={[styles.channelTitle, { color: black }, channelTitle]}
testID='empty-channel-state-title'
>
{t("Let's start chatting!")}
</Text>
<Text
style={[styles.channelDetails, { color: grey, width: vw(66) }, channelDetails]}
testID='empty-channel-state-details'
>
{t('How about sending your first message to a friend?')}
<MessageBubbleEmpty height={27} stroke={semantics.textTertiary} width={25} />
<Text style={[styles.channelTitle, channelTitle]} testID='empty-channel-state-title'>
{t('No conversations yet')}
</Text>
</View>
);
case 'message':
return (
<View style={[styles.container, messageContainer]}>
<ChatIcon height={width} pathFill={grey_gainsboro} width={width} />
<Text style={[styles.messageTitle, { color: grey_gainsboro }, messageTitle]}>
{t('No chats here yet…')}
</Text>
<MessageBubbleEmpty height={27} stroke={semantics.textTertiary} width={25} />
<Text style={[styles.messageTitle, messageTitle]}>{t('No chats here yet…')}</Text>
</View>
);
case 'threads':
return (
<View style={[styles.container]}>
<MessageBubbleEmpty height={width} pathFill={'#B4BBBA'} width={width} />
<Text style={{ color: '#7E828B' }}>{t('No threads here yet')}...</Text>
<View style={styles.container}>
<MessageBubbleEmpty height={27} stroke={semantics.textTertiary} width={25} />
<Text style={styles.threadText}>{t('Reply to a message to start a thread')}</Text>
</View>
);
default:
return <Text style={[{ color: black }, messageContainer]}>No items exist</Text>;
return (
<Text style={[{ color: semantics.textSecondary }, messageContainer]}>No items exist</Text>
);
}
};

const styles = StyleSheet.create({
channelDetails: {
fontSize: 14,
textAlign: 'center',
},
channelTitle: {
fontSize: 16,
paddingBottom: 8,
paddingTop: 16,
},
container: {
alignItems: 'center',
flex: 1,
justifyContent: 'center',
},
messageTitle: {
fontSize: 20,
fontWeight: 'bold',
paddingBottom: 8,
},
});
const useStyles = () => {
const {
theme: { semantics },
} = useTheme();

return useMemo(() => {
return StyleSheet.create({
channelDetails: {
fontSize: 14,
textAlign: 'center',
},
channelTitle: {
color: semantics.textSecondary,
fontSize: primitives.typographyFontSizeMd,
fontWeight: primitives.typographyFontWeightRegular,
lineHeight: primitives.typographyLineHeightNormal,
textAlign: 'center',
paddingVertical: primitives.spacingSm,
},
container: {
alignItems: 'center',
flex: 1,
justifyContent: 'center',
},
messageTitle: {
fontSize: 20,
fontWeight: 'bold',
paddingBottom: 8,
},
threadText: {
color: semantics.textSecondary,
fontSize: primitives.typographyFontSizeMd,
fontWeight: primitives.typographyFontWeightRegular,
lineHeight: primitives.typographyLineHeightNormal,
textAlign: 'center',
paddingVertical: primitives.spacingSm,
},
});
}, [semantics]);
};
Loading
Loading