diff --git a/components/ConversationListItem/ConversationListItemDumb.tsx b/components/ConversationListItem/ConversationListItemDumb.tsx index d89f46415..f7e378b3b 100644 --- a/components/ConversationListItem/ConversationListItemDumb.tsx +++ b/components/ConversationListItem/ConversationListItemDumb.tsx @@ -1,34 +1,19 @@ -import { - backgroundColor, - badgeColor, - clickedItemBackgroundColor, - dangerColor, - inversePrimaryColor, - textPrimaryColor, - textSecondaryColor, -} from "@styles/colors"; -import { PictoSizes } from "@styles/sizes"; -import { Image } from "expo-image"; -import React, { forwardRef, memo, useCallback, useMemo } from "react"; -import { - LayoutChangeEvent, - Platform, - StyleSheet, - Text, - TouchableHighlight, - useColorScheme, - View, -} from "react-native"; +// import { Image } from "expo-image"; +import React, { forwardRef, memo, useCallback } from "react"; +import { Platform, TouchableHighlight, ViewStyle } from "react-native"; import { RectButton } from "react-native-gesture-handler"; import Swipeable from "react-native-gesture-handler/Swipeable"; import { TouchableRipple } from "react-native-paper"; -import Animated, { - useSharedValue, - useAnimatedRef, -} from "react-native-reanimated"; import { IIconName } from "@design-system/Icon/Icon.types"; import { Icon } from "@design-system/Icon/Icon"; +import { Text } from "@/design-system/Text"; +import { Center } from "@/design-system/Center"; +import { HStack } from "@/design-system/HStack"; +import { VStack } from "@/design-system/VStack"; +import { ThemedStyle, useAppTheme } from "@/theme/useAppTheme"; +import { ImageStyle } from "expo-image"; +import { clickedItemBackgroundColor } from "@/styles/colors"; export type ConversationListItemDumbProps = { title?: string; @@ -51,156 +36,6 @@ export type ConversationListItemDumbProps = { imagePreviewUrl: string | undefined; }; -const useStyles = () => { - const colorScheme = useColorScheme(); - return useMemo( - () => - StyleSheet.create({ - rowSeparator: Platform.select({ - android: {}, - default: { - height: 80, - }, - }), - rowSeparatorMargin: { - position: "absolute", - width: 84, - height: 2, - backgroundColor: backgroundColor(colorScheme), - bottom: -1.5, - }, - conversationListItem: { - flexDirection: "row", - height: "100%", - paddingRight: 16, - }, - avatarWrapper: { - marginLeft: 16, - alignSelf: "center", - }, - messagePreviewContainer: { - flexGrow: 1, - flexShrink: 1, - paddingRight: 16, - ...Platform.select({ - default: { - height: 84, - paddingTop: 12, - marginLeft: 12, - }, - android: { - height: 72, - paddingTop: 16.5, - paddingLeft: 16, - }, - }), - }, - conversationName: { - color: textPrimaryColor(colorScheme), - ...Platform.select({ - default: { - fontSize: 17, - fontWeight: "600", - marginBottom: 3, - }, - android: { - fontSize: 16, - }, - }), - }, - messagePreview: { - color: textSecondaryColor(colorScheme), - ...Platform.select({ - default: { - fontSize: 15, - marginBottom: 8, - }, - android: { - fontSize: 14, - }, - }), - }, - timeText: { - color: textSecondaryColor(colorScheme), - ...Platform.select({ - default: { fontSize: 15 }, - web: { marginRight: 14, fontSize: 15 }, - android: { fontSize: 11 }, - }), - }, - unreadContainer: { - flexDirection: "row", - alignItems: "center", - justifyContent: "flex-end", - marginLeft: 16, - }, - unread: { - width: 14, - height: 14, - borderRadius: 16, - backgroundColor: badgeColor(colorScheme), - flexDirection: "row", - justifyContent: "center", - alignItems: "center", - }, - placeholder: { - backgroundColor: "transparent", - }, - rightAction: { - width: 100, - alignItems: "center", - backgroundColor: badgeColor(colorScheme), - justifyContent: "center", - }, - rightActionRed: { - width: 100, - alignItems: "center", - backgroundColor: dangerColor(colorScheme), - justifyContent: "center", - }, - leftAction: { - width: 100, - alignItems: "center", - backgroundColor: badgeColor(colorScheme), - justifyContent: "center", - }, - rippleRow: { - backgroundColor: backgroundColor(colorScheme), - }, - imagePreviewContainer: { - flexDirection: "row", - alignItems: "center", - justifyContent: "flex-end", - marginLeft: 16, - }, - imagePreview: { - height: 56, - width: 56, - borderRadius: 4, - aspectRatio: 1, - }, - }), - [colorScheme] - ); -}; - -const useDisplayInfo = () => { - const colorScheme = useColorScheme(); - const displayRowSeparator = Platform.OS === "ios"; - const themedClickedItemBackgroundColor = - clickedItemBackgroundColor(colorScheme); - const themedDangerColor = dangerColor(colorScheme); - const themedInversePrimaryColor = inversePrimaryColor(colorScheme); - const themedBackgroundColor = backgroundColor(colorScheme); - return { - displayRowSeparator, - themedClickedItemBackgroundColor, - themedDangerColor, - themedInversePrimaryColor, - themedBackgroundColor, - }; -}; - export const ConversationListItemDumb = memo( forwardRef( function ConversationListItemDumb( @@ -225,38 +60,26 @@ export const ConversationListItemDumb = memo( }, swipeableRef ) { - const styles = useStyles(); - const { - displayRowSeparator, - themedDangerColor, - themedInversePrimaryColor, - themedClickedItemBackgroundColor, - themedBackgroundColor, - } = useDisplayInfo(); - - const itemRect = useSharedValue({ x: 0, y: 0, width: 0, height: 0 }); - const containerRef = useAnimatedRef(); - - const onLayoutView = useCallback( - (event: LayoutChangeEvent) => { - const { x, y, width, height } = event.nativeEvent.layout; - itemRect.value = { x, y, width, height }; - }, - [itemRect] + const { themed, theme } = useAppTheme(); + const { iconSize } = theme; + const themedClickedItemBackgroundColor = clickedItemBackgroundColor( + theme.isDark ? "dark" : "light" ); + const themedInversePrimaryColor = theme.colors.fill.inverted.primary; + const listItemContent = ( - - {avatarComponent} - - + +
{avatarComponent}
+ + {title} - + {subtitle} -
- {showImagePreview && ( + + {/* {showImagePreview && ( - )} + )} */} {(isUnread || showError) && ( - - +
- {showError && ( - - )} - - + /> +
)} -
+ ); const renderLeftActions = useCallback(() => { return ( - + ); - }, [leftActionIcon, styles.leftAction, themedInversePrimaryColor]); + }, [leftActionIcon, themed, themedInversePrimaryColor, iconSize.lg]); const renderRightActions = useCallback(() => { if (rightIsDestructive) { return ( - + ); } else { return ( - + ); } }, [ rightIsDestructive, - styles.rightAction, - styles.rightActionRed, + themed, onRightActionPress, themedInversePrimaryColor, + iconSize.lg, ]); const rowItem = ( - + <> {Platform.OS === "ios" ? ( {listItemContent} @@ -347,13 +162,13 @@ export const ConversationListItemDumb = memo( unstable_pressDelay={75} onPress={onPress} onLongPress={onLongPress} - style={styles.rippleRow} + style={themed($touchable)} rippleColor={themedClickedItemBackgroundColor} > {listItemContent} )} - + ); const onSwipeableWillClose = useCallback( @@ -379,23 +194,88 @@ export const ConversationListItemDumb = memo( ); return ( - - - {rowItem} - - {/* Hide part of the border to mimic margin*/} - {displayRowSeparator && } - + + {rowItem} + ); } ) ); + +const $conversationListItem: ThemedStyle = ({ spacing }) => ({ + paddingHorizontal: spacing.lg, + paddingVertical: spacing.xs, +}); + +const $avatarWrapper: ViewStyle = { + alignSelf: "center", +}; + +const $messagePreviewContainer: ThemedStyle = ({ spacing }) => ({ + flexGrow: 1, + flexShrink: 1, + marginLeft: spacing.xs, +}); + +const $unreadContainer: ViewStyle = { + alignItems: "center", +}; + +const $unread: ThemedStyle = ({ spacing, colors }) => ({ + width: spacing.sm, + height: spacing.sm, + borderRadius: spacing.xs, + backgroundColor: colors.fill.primary, +}); + +const $touchable: ThemedStyle = ({ colors }) => ({ + backgroundColor: colors.background.surface, +}); + +const $placeholder: ThemedStyle = ({ colors }) => ({ + backgroundColor: colors.global.transparent, +}); + +const ACTION_WIDTH = 100; + +const $rightAction: ThemedStyle = ({ colors }) => ({ + width: ACTION_WIDTH, + alignItems: "center", + backgroundColor: colors.fill.primary, + justifyContent: "center", +}); + +const $rightActionRed: ThemedStyle = ({ colors }) => ({ + width: ACTION_WIDTH, + alignItems: "center", + backgroundColor: colors.fill.caution, + justifyContent: "center", +}); + +const $leftAction: ThemedStyle = ({ colors }) => ({ + width: ACTION_WIDTH, + alignItems: "center", + backgroundColor: colors.fill.primary, + justifyContent: "center", +}); + +const $imagePreviewContainer: ViewStyle = { + flexDirection: "row", + alignItems: "center", + justifyContent: "flex-end", +}; + +const $imagePreview: ThemedStyle = ({ spacing }) => ({ + height: spacing["4xl"], + width: spacing["4xl"], + borderRadius: spacing.xxxs, +}); diff --git a/components/V3DMListItem.tsx b/components/V3DMListItem.tsx index 7d2afdcc3..daee756eb 100644 --- a/components/V3DMListItem.tsx +++ b/components/V3DMListItem.tsx @@ -157,7 +157,6 @@ export const V3DMListItem = ({ conversation }: V3DMListItemProps) => { size={AvatarSizes.conversationListItem} uri={avatarUri} name={preferredName} - style={{ marginLeft: 16, alignSelf: "center" }} /> ); }, [avatarUri, preferredName]); @@ -197,6 +196,9 @@ export const V3DMListItem = ({ conversation }: V3DMListItemProps) => { const onRightSwipe = useCallback(() => {}, []); + const subtitle = + timeToShow && messageText ? `${timeToShow} ⋅ ${messageText}` : ""; + return ( { imagePreviewUrl={undefined} avatarComponent={avatarComponent} title={preferredName} - subtitle={`${timeToShow} ⋅ ${messageText}`} + subtitle={subtitle} isUnread={isUnread} rightIsDestructive={isBlockedChatView} /> diff --git a/components/V3GroupConversationListItem.tsx b/components/V3GroupConversationListItem.tsx index 3371e1ee0..f3b7e8eea 100644 --- a/components/V3GroupConversationListItem.tsx +++ b/components/V3GroupConversationListItem.tsx @@ -402,17 +402,18 @@ export function V3GroupConversationListItem({ ) : ( ); }, [group?.imageUrlSquare, memberData]); + const subtitle = + timeToShow && messageText ? `${timeToShow} ⋅ ${messageText}` : ""; + return ( diff --git a/features/conversation-list/hooks/useMessageText.ts b/features/conversation-list/hooks/useMessageText.ts index 6c7fbb9f4..106bfe15d 100644 --- a/features/conversation-list/hooks/useMessageText.ts +++ b/features/conversation-list/hooks/useMessageText.ts @@ -1,17 +1,46 @@ import { + isGroupUpdatedMessage, + isRemoteAttachmentMessage, isReplyMessage, isStaticAttachmentMessage, } from "@/features/conversation/conversation-message/conversation-message.utils"; import logger from "@utils/logger"; import { DecodedMessageWithCodecsType } from "@/utils/xmtpRN/client.types"; -import { getMessageContentType } from "@/utils/xmtpRN/content-types/content-types"; -import { DecodedMessage, ReplyCodec } from "@xmtp/react-native-sdk"; +import { DecodedMessage, InboxId, ReplyCodec } from "@xmtp/react-native-sdk"; import { useMemo } from "react"; +import { translate } from "@/i18n"; +import { + getInboxProfileSocialsQueryData, + useInboxProfileSocialsQueries, +} from "@/queries/useInboxProfileSocialsQuery"; +import { useCurrentAccount } from "@/data/store/accountsStore"; +import { getPreferredInboxName } from "@/utils/profile"; export const useMessageText = ( message: DecodedMessageWithCodecsType | undefined ) => { + const account = useCurrentAccount(); + + const inboxIds = useMemo(() => { + if (!message) return []; + if (isGroupUpdatedMessage(message)) { + const idsToFetch: InboxId[] = []; + const content = message.content(); + idsToFetch.push(content.initiatedByInboxId); + content.membersAdded.forEach((member) => { + idsToFetch.push(member.inboxId); + }); + content.membersRemoved.forEach((member) => { + idsToFetch.push(member.inboxId); + }); + return idsToFetch; + } + return []; + }, [message]); + useInboxProfileSocialsQueries(account!, inboxIds); + return useMemo(() => { + if (!account) return ""; if (!message) return ""; try { @@ -25,15 +54,95 @@ export const useMessageText = ( return content.content.text; } - if (isStaticAttachmentMessage(message)) { - return "Attachment"; + if ( + isStaticAttachmentMessage(message) || + isRemoteAttachmentMessage(message) + ) { + return translate("conversation_list.attachment"); } const content = message?.content(); - const contentType = getMessageContentType(message.contentTypeId); - if (contentType === "groupUpdated") { - // TODO: Update this - return "conversation updated"; + if (isGroupUpdatedMessage(message)) { + const content = message.content(); + const initiatorSocials = getInboxProfileSocialsQueryData( + account, + content.initiatedByInboxId + ); + const initiatorName = getPreferredInboxName(initiatorSocials); + if (!initiatorName) return translate("conversation_list.group_updated"); + if (content.metadataFieldsChanged.length > 0) { + if (content.metadataFieldsChanged.length === 1) { + switch (content.metadataFieldsChanged[0].fieldName) { + case "group_name": + return translate("conversation_list.name_changed", { + userName: initiatorName, + newValue: content.metadataFieldsChanged[0].newValue, + }); + + case "description": + return translate("conversation_list.description_changed", { + userName: initiatorName, + newValue: content.metadataFieldsChanged[0].newValue, + }); + case "group_image_url_square": + return translate("conversation_list.image_changed", { + userName: initiatorName, + }); + + default: + return translate("conversation_list.updated_the_group", { + userName: initiatorName, + }); + } + } + return translate("conversation_list.updated_the_group", { + userName: initiatorName, + }); + } + if (content.membersAdded.length > 0) { + if (content.membersAdded.length === 1) { + const memberSocials = getInboxProfileSocialsQueryData( + account, + content.membersAdded[0].inboxId + ); + const memberName = getPreferredInboxName(memberSocials); + if (!memberName) + return translate("conversation_list.member_added_unknown", { + userName: initiatorName, + }); + return translate("conversation_list.member_added", { + userName: initiatorName, + memberName: memberName, + }); + } + return translate("conversation_list.members_added", { + userName: initiatorName, + count: content.membersAdded.length, + }); + } + if (content.membersRemoved.length > 0) { + if (content.membersRemoved.length === 1) { + const memberSocials = getInboxProfileSocialsQueryData( + account, + content.membersRemoved[0].inboxId + ); + const memberName = getPreferredInboxName(memberSocials); + if (!memberName) + return translate("conversation_list.member_removed_unknown", { + userName: initiatorName, + }); + return translate("conversation_list.member_removed", { + userName: initiatorName, + memberName: memberName, + }); + } + return translate("conversation_list.members_removed", { + userName: initiatorName, + count: content.membersRemoved.length, + }); + } + + return translate("conversation_list.group_updated"); } if (typeof content === "string") { return content; @@ -47,5 +156,5 @@ export const useMessageText = ( }); return message.fallback; } - }, [message]); + }, [message, account]); }; diff --git a/i18n/translations/en.ts b/i18n/translations/en.ts index ac6aa39f8..4df8781ec 100644 --- a/i18n/translations/en.ts +++ b/i18n/translations/en.ts @@ -508,6 +508,19 @@ export const en = { conversation_list: { messages: "Messages", + attachment: "Attachment", + updated_the_group: "{{userName}} updated the group", + group_updated: "Group updated", + name_changed: "{{userName}} changed the group name to {{newValue}}", + description_changed: + "{{userName}} changed the group description to {{newValue}}", + image_changed: "{{userName}} changed the group image", + member_added: "{{userName}} added {{memberName}} to the group", + member_added_unknown: "{{userName}} added a new member to the group", + members_added: "{{userName}} added {{count}} members to the group", + member_removed: "{{userName}} removed {{memberName}} from the group", + member_removed_unknown: "{{userName}} removed a member from the group", + members_removed: "{{userName}} removed {{count}} members from the group", }, debug: { diff --git a/i18n/translations/fr.ts b/i18n/translations/fr.ts index e8b65e13f..512e6a000 100644 --- a/i18n/translations/fr.ts +++ b/i18n/translations/fr.ts @@ -513,6 +513,19 @@ export const fr = { conversation_list: { messages: "Messages", + attachment: "Pièce jointe", + updated_the_group: "{{userName}} a mis à jour le groupe", + group_updated: "Groupe mis à jour", + name_changed: "{{userName}} a changé le nom du groupe en {{newValue}}", + description_changed: + "{{userName}} a changé la description du groupe en {{newValue}}", + image_changed: "{{userName}} a changé l'image du groupe", + member_added: "{{userName}} a ajouté {{memberName}} au groupe", + member_added_unknown: "{{userName}} a ajouté un nouveau membre au groupe", + members_added: "{{userName}} a ajouté {{count}} membres au groupe", + member_removed: "{{userName}} a retiré {{memberName}} du groupe", + member_removed_unknown: "{{userName}} a retiré un membre du groupe", + members_removed: "{{userName}} a retiré {{count}} membres du groupe", }, debug: { diff --git a/styles/sizes/index.ts b/styles/sizes/index.ts index 1224891a1..cc48c8722 100644 --- a/styles/sizes/index.ts +++ b/styles/sizes/index.ts @@ -5,7 +5,7 @@ import { Platform } from "react-native"; */ export enum AvatarSizes { default = 121, - conversationListItem = Platform.OS === "ios" ? 56 : 47, + conversationListItem = 56, conversationTitle = 30, listItemDisplay = 40, messageSender = Platform.OS === "ios" ? 30 : 21, @@ -18,7 +18,7 @@ export enum AvatarSizes { } /** - * @deprecated + * @deprecated - Use iconSize from theme instead */ export enum PictoSizes { default = 48,