diff --git a/components/ConversationFlashList/ConversationFlashList.tsx b/components/ConversationFlashList/ConversationFlashList.tsx
index 722e15397..82ee9c1aa 100644
--- a/components/ConversationFlashList/ConversationFlashList.tsx
+++ b/components/ConversationFlashList/ConversationFlashList.tsx
@@ -19,14 +19,9 @@ import { useSelect } from "@data/store/storeHelpers";
import { NavigationParamList } from "@screens/Navigation/Navigation";
import { ConversationFlatListHiddenRequestItem } from "@utils/conversation";
import { FlatListItemType } from "@features/conversation-list/ConversationList.types";
-import { unwrapConversationContainer } from "@utils/groupUtils/conversationContainerHelpers";
-import { ConversationVersion } from "@xmtp/react-native-sdk";
-import {
- DmWithCodecsType,
- GroupWithCodecsType,
-} from "@/utils/xmtpRN/client.types";
import { V3DMListItem } from "../V3DMListItem";
import { CONVERSATION_FLASH_LIST_REFRESH_THRESHOLD } from "./ConversationFlashList.constants";
+import { isConversationGroup } from "@/features/conversation/utils/is-conversation-group";
type Props = {
onScroll?: () => void;
@@ -73,15 +68,10 @@ export default function ConversationFlashList({
const renderItem = useCallback(({ item }: { item: FlatListItemType }) => {
if ("lastMessage" in item) {
- const conversation = unwrapConversationContainer(item);
- if (conversation.version === ConversationVersion.GROUP) {
- return (
-
- );
+ if (isConversationGroup(item)) {
+ return ;
} else {
- return ;
+ return ;
}
}
if (item.topic === "hiddenRequestsButton") {
diff --git a/components/PinnedConversations/PinnedV3Conversation.tsx b/components/PinnedConversations/PinnedV3Conversation.tsx
index 8edc8e3ba..2b99e1066 100644
--- a/components/PinnedConversations/PinnedV3Conversation.tsx
+++ b/components/PinnedConversations/PinnedV3Conversation.tsx
@@ -1,13 +1,9 @@
import { useConversationListGroupItem } from "@hooks/useConversationListGroupItem";
-import { conversationIsGroup } from "@utils/groupUtils/conversationContainerHelpers";
-import {
- DmWithCodecsType,
- GroupWithCodecsType,
-} from "@/utils/xmtpRN/client.types";
import React from "react";
import { PinnedV3GroupConversation } from "./PinnedV3GroupConversation";
import { PinnedV3DMConversation } from "./PinnedV3DMConversation";
import type { ConversationTopic } from "@xmtp/react-native-sdk";
+import { isConversationGroup } from "@/features/conversation/utils/is-conversation-group";
type PinnedV3ConversationProps = {
topic: string;
@@ -18,12 +14,8 @@ export const PinnedV3Conversation = ({ topic }: PinnedV3ConversationProps) => {
if (!conversation) {
return null;
}
- if (conversationIsGroup(conversation)) {
- return (
-
- );
+ if (isConversationGroup(conversation)) {
+ return ;
}
- return (
-
- );
+ return ;
};
diff --git a/features/notifications/utils/background/groupMessageNotification.ts b/features/notifications/utils/background/groupMessageNotification.ts
index 089a30e28..70c5e38e9 100644
--- a/features/notifications/utils/background/groupMessageNotification.ts
+++ b/features/notifications/utils/background/groupMessageNotification.ts
@@ -11,25 +11,201 @@ import {
} from "@utils/profile";
import {
ConverseXmtpClientType,
+ DecodedMessageWithCodecsType,
+ DmWithCodecsType,
GroupWithCodecsType,
} from "@utils/xmtpRN/client.types";
-import {
- ConversationVersion,
- Group,
- type ConversationTopic,
-} from "@xmtp/react-native-sdk";
+import { Conversation, type ConversationTopic } from "@xmtp/react-native-sdk";
import { androidChannel } from "../setupAndroidNotificationChannel";
import { notificationAlreadyShown } from "./alreadyShown";
import { getNotificationContent } from "./notificationContent";
import { computeSpamScoreGroupMessage } from "./notificationSpamScore";
import { ProtocolNotification } from "./protocolNotification";
import logger from "@/utils/logger";
+import { isConversationGroup } from "@/features/conversation/utils/is-conversation-group";
+
+const getSenderProfileInfo = async (
+ xmtpClient: ConverseXmtpClientType,
+ conversation: Conversation,
+ message: DecodedMessageWithCodecsType
+) => {
+ // For now, use the group member linked address as "senderAddress"
+ // @todo => make inboxId a first class citizen
+ const senderAddress = (await conversation.members()).find(
+ (m) => m.inboxId === message.senderInboxId
+ )?.addresses[0];
+ if (!senderAddress)
+ return {
+ senderName: "",
+ };
+ const senderSocials = await getProfile(
+ xmtpClient.address,
+ message.senderInboxId,
+ senderAddress
+ );
+ const senderName = getPreferredName(senderSocials, senderAddress);
+ const senderImage = getPreferredAvatar(senderSocials);
+
+ return {
+ senderName,
+ senderImage,
+ };
+};
+
+type INotificationFunctionPayloads = {
+ notificationContent: string;
+ notification: ProtocolNotification;
+ conversation: GroupWithCodecsType | DmWithCodecsType;
+ message: DecodedMessageWithCodecsType;
+ senderName: string;
+ senderImage?: string;
+};
+
+type IHandleGroupMessageNotification = INotificationFunctionPayloads & {
+ conversation: GroupWithCodecsType;
+};
+
+const handleGroupMessageNotification = async ({
+ notificationContent,
+ notification,
+ conversation,
+ message,
+ senderName,
+ senderImage,
+}: IHandleGroupMessageNotification) => {
+ const groupName = await conversation.groupName();
+ const groupImage = await conversation.groupImageUrlSquare();
+ const person: AndroidPerson = {
+ name: senderName,
+ };
+ if (senderImage) {
+ person.icon = senderImage;
+ }
+
+ const withLargeIcon = groupImage
+ ? {
+ largeIcon: groupImage,
+ circularLargeIcon: true,
+ }
+ : {};
+ const displayedNotifications = await notifee.getDisplayedNotifications();
+ const previousGroupIdNotification =
+ conversation.topic &&
+ displayedNotifications.find(
+ (n) => n.notification.android?.groupId === conversation.topic
+ );
+ const previousMessages =
+ previousGroupIdNotification?.notification.android?.style?.type ===
+ AndroidStyle.MESSAGING
+ ? previousGroupIdNotification.notification.android?.style?.messages
+ : [];
+ if (previousGroupIdNotification?.notification.id) {
+ notifee.cancelDisplayedNotification(
+ previousGroupIdNotification.notification.id
+ );
+ }
+ await notifee.displayNotification({
+ title: groupName,
+ subtitle: senderName,
+ body: notificationContent,
+ data: notification,
+ android: {
+ channelId: androidChannel.id,
+ groupId: conversation.topic,
+ timestamp: normalizeTimestamp(message.sentNs),
+ showTimestamp: true,
+ pressAction: {
+ id: "default",
+ },
+ visibility: AndroidVisibility.PUBLIC,
+ ...withLargeIcon,
+ style: {
+ type: AndroidStyle.MESSAGING,
+ person: person,
+ messages: [
+ ...previousMessages,
+ {
+ text: notificationContent,
+ timestamp: normalizeTimestamp(message.sentNs),
+ },
+ ],
+ group: true,
+ },
+ },
+ });
+};
+
+type IHandleDmMessageNotification = INotificationFunctionPayloads & {
+ conversation: DmWithCodecsType;
+};
-export const isGroupMessageContentTopic = (contentTopic: string) => {
- return contentTopic.startsWith("/xmtp/mls/1/g-");
+const handleDmMessageNotification = async ({
+ notificationContent,
+ notification,
+ conversation,
+ message,
+ senderName,
+ senderImage,
+}: IHandleDmMessageNotification) => {
+ const person: AndroidPerson = {
+ name: senderName,
+ };
+ if (senderImage) {
+ person.icon = senderImage;
+ }
+ const withLargeIcon = senderImage
+ ? {
+ largeIcon: senderImage,
+ circularLargeIcon: true,
+ }
+ : {};
+ const displayedNotifications = await notifee.getDisplayedNotifications();
+ const previousGroupIdNotification =
+ conversation?.topic &&
+ displayedNotifications.find(
+ (n) => n.notification.android?.groupId === conversation?.topic
+ );
+ const previousMessages =
+ previousGroupIdNotification?.notification.android?.style?.type ===
+ AndroidStyle.MESSAGING
+ ? previousGroupIdNotification.notification.android?.style?.messages
+ : [];
+ if (previousGroupIdNotification?.notification.id) {
+ notifee.cancelDisplayedNotification(
+ previousGroupIdNotification.notification.id
+ );
+ }
+ await notifee.displayNotification({
+ title: senderName,
+ body: notificationContent,
+ data: notification,
+ android: {
+ channelId: androidChannel.id,
+ groupId: conversation.topic,
+ timestamp: normalizeTimestamp(message.sentNs),
+ showTimestamp: true,
+ pressAction: {
+ id: "default",
+ },
+ visibility: AndroidVisibility.PUBLIC,
+ ...withLargeIcon,
+ style: {
+ type: AndroidStyle.MESSAGING,
+ person,
+ messages: [
+ ...previousMessages,
+ {
+ text: notificationContent,
+ timestamp: normalizeTimestamp(message.sentNs),
+ },
+ ],
+ group: false,
+ },
+ },
+ });
};
-export const handleGroupMessageNotification = async (
+export const handleV3MessageNotification = async (
xmtpClient: ConverseXmtpClientType,
notification: ProtocolNotification
) => {
@@ -45,7 +221,6 @@ export const handleGroupMessageNotification = async (
if (!conversation) throw new Error("Conversation not found");
}
await conversation.sync();
- const isGroup = conversation.version === ConversationVersion.GROUP;
const message = await conversation.processMessage(notification.message);
// Not displaying notifications for ourselves, syncing is enough
@@ -59,144 +234,35 @@ export const handleGroupMessageNotification = async (
message
);
if (spamScore >= 0) return;
- // For now, use the group member linked address as "senderAddress"
- // @todo => make inboxId a first class citizen
- const senderAddress = (await conversation.members()).find(
- (m) => m.inboxId === message.senderInboxId
- )?.addresses[0];
- if (!senderAddress) return;
- const senderSocials = await getProfile(
- xmtpClient.address,
- message.senderInboxId,
- senderAddress
- );
- const senderName = getPreferredName(senderSocials, senderAddress);
- const senderImage = getPreferredAvatar(senderSocials);
const notificationContent = await getNotificationContent(
conversation as GroupWithCodecsType,
message
);
if (!notificationContent) return;
+ const { senderName, senderImage } = await getSenderProfileInfo(
+ xmtpClient,
+ conversation,
+ message
+ );
- if (isGroup) {
- const groupName = await (conversation as Group).groupName();
- const groupImage = await (conversation as Group).groupImageUrlSquare();
- const person: AndroidPerson = {
- name: senderName,
- };
- if (senderImage) {
- person.icon = senderImage;
- }
-
- const withLargeIcon = groupImage
- ? {
- largeIcon: groupImage,
- circularLargeIcon: true,
- }
- : {};
- const displayedNotifications = await notifee.getDisplayedNotifications();
- const previousGroupIdNotification =
- conversation?.topic &&
- displayedNotifications.find(
- (n) => n.notification.android?.groupId === conversation?.topic
- );
- const previousMessages =
- previousGroupIdNotification?.notification.android?.style?.type ===
- AndroidStyle.MESSAGING
- ? previousGroupIdNotification.notification.android?.style?.messages
- : [];
- if (previousGroupIdNotification?.notification.id) {
- notifee.cancelDisplayedNotification(
- previousGroupIdNotification.notification.id
- );
- }
- await notifee.displayNotification({
- title: groupName,
- subtitle: senderName,
- body: notificationContent,
- data: notification,
- android: {
- channelId: androidChannel.id,
- groupId: conversation.topic,
- timestamp: normalizeTimestamp(message.sentNs),
- showTimestamp: true,
- pressAction: {
- id: "default",
- },
- visibility: AndroidVisibility.PUBLIC,
- ...withLargeIcon,
- style: {
- type: AndroidStyle.MESSAGING,
- person: person,
- messages: [
- ...previousMessages,
- {
- text: notificationContent,
- timestamp: normalizeTimestamp(message.sentNs),
- },
- ],
- group: true,
- },
- },
+ if (isConversationGroup(conversation)) {
+ handleGroupMessageNotification({
+ notificationContent,
+ notification,
+ conversation,
+ message,
+ senderName,
+ senderImage,
});
} else {
- const senderImage = getPreferredAvatar(senderSocials);
- const person: AndroidPerson = {
- name: senderName,
- };
- if (senderImage) {
- person.icon = senderImage;
- }
- const withLargeIcon = senderImage
- ? {
- largeIcon: senderImage,
- circularLargeIcon: true,
- }
- : {};
- const displayedNotifications = await notifee.getDisplayedNotifications();
- const previousGroupIdNotification =
- conversation?.topic &&
- displayedNotifications.find(
- (n) => n.notification.android?.groupId === conversation?.topic
- );
- const previousMessages =
- previousGroupIdNotification?.notification.android?.style?.type ===
- AndroidStyle.MESSAGING
- ? previousGroupIdNotification.notification.android?.style?.messages
- : [];
- if (previousGroupIdNotification?.notification.id) {
- notifee.cancelDisplayedNotification(
- previousGroupIdNotification.notification.id
- );
- }
- await notifee.displayNotification({
- title: senderName,
- body: notificationContent,
- data: notification,
- android: {
- channelId: androidChannel.id,
- groupId: conversation.topic,
- timestamp: normalizeTimestamp(message.sentNs),
- showTimestamp: true,
- pressAction: {
- id: "default",
- },
- visibility: AndroidVisibility.PUBLIC,
- ...withLargeIcon,
- style: {
- type: AndroidStyle.MESSAGING,
- person,
- messages: [
- ...previousMessages,
- {
- text: notificationContent,
- timestamp: normalizeTimestamp(message.sentNs),
- },
- ],
- group: false,
- },
- },
+ handleDmMessageNotification({
+ notificationContent,
+ notification,
+ conversation,
+ message,
+ senderName,
+ senderImage,
});
}
} catch (e) {
diff --git a/features/notifications/utils/background/protocolNotification.ts b/features/notifications/utils/background/protocolNotification.ts
index b8eec943e..4c589a99f 100644
--- a/features/notifications/utils/background/protocolNotification.ts
+++ b/features/notifications/utils/background/protocolNotification.ts
@@ -3,14 +3,12 @@ import { ConverseXmtpClientType } from "@/utils/xmtpRN/client.types";
import { getXmtpClient } from "@utils/xmtpRN/sync";
import { z } from "zod";
import logger from "@utils/logger";
-import {
- handleGroupMessageNotification,
- isGroupMessageContentTopic,
-} from "./groupMessageNotification";
+import { handleV3MessageNotification } from "./groupMessageNotification";
import {
handleGroupWelcomeNotification,
isGroupWelcomeContentTopic,
} from "./groupWelcomeNotification";
+import { isV3Topic } from "@/utils/groupUtils/groupId";
export const ProtocolNotificationSchema = z.object({
account: z.string().regex(/^0x[a-fA-F0-9]{40}$/),
@@ -44,8 +42,8 @@ export const handleProtocolNotification = async (
const xmtpClient = (await getXmtpClient(
notification.account
)) as ConverseXmtpClientType;
- if (isGroupMessageContentTopic(notification.contentTopic)) {
- handleGroupMessageNotification(xmtpClient, notification);
+ if (isV3Topic(notification.contentTopic)) {
+ handleV3MessageNotification(xmtpClient, notification);
} else if (isGroupWelcomeContentTopic(notification.contentTopic)) {
handleGroupWelcomeNotification(xmtpClient, notification);
}
diff --git a/utils/groupUtils/conversationContainerHelpers.ts b/utils/groupUtils/conversationContainerHelpers.ts
deleted file mode 100644
index d08df1f66..000000000
--- a/utils/groupUtils/conversationContainerHelpers.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import {
- ConversationWithCodecsType,
- DmWithCodecsType,
- GroupWithCodecsType,
-} from "../xmtpRN/client.types";
-import { ConversationVersion } from "@xmtp/react-native-sdk";
-
-export const unwrapConversationContainer = (
- conversation: ConversationWithCodecsType
-) => {
- if (conversationIsGroup(conversation)) {
- return conversation as GroupWithCodecsType;
- }
- return conversation as DmWithCodecsType;
-};
-
-export const conversationIsGroup = (
- conversation: ConversationWithCodecsType
-) => {
- return conversation.version === ConversationVersion.GROUP;
-};