Skip to content

Commit

Permalink
fix: Frames Renders on initial Send (#1113)
Browse files Browse the repository at this point in the history
* fix: Frames Renders on initial Send

Updated to use zustand store for managing frames
Updated to use hasFrames rather than isFrame

* cleanup
  • Loading branch information
alexrisch authored Oct 28, 2024
1 parent 6825113 commit 6b95e36
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 77 deletions.
24 changes: 14 additions & 10 deletions components/Chat/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
tertiaryBackgroundColor,
} from "@styles/colors";
import { getCleanAddress } from "@utils/evm/address";
import { FrameWithType } from "@utils/frames";
import { FrameWithType, messageHasFrames } from "@utils/frames";
import differenceInCalendarDays from "date-fns/differenceInCalendarDays";
import React, { useCallback, useEffect, useMemo, useRef } from "react";
import {
Expand Down Expand Up @@ -72,13 +72,13 @@ const usePeerSocials = () => {
const useRenderItem = ({
xmtpAddress,
conversation,
framesStore,
messageFramesMap,
colorScheme,
}: {
xmtpAddress: string;
conversation: XmtpConversationWithUpdate | undefined;
framesStore: {
[frameUrl: string]: FrameWithType;
messageFramesMap: {
[messageId: string]: FrameWithType[];
};
colorScheme: ColorSchemeName;
}) => {
Expand All @@ -90,11 +90,11 @@ const useRenderItem = ({
message={{ ...item }}
colorScheme={colorScheme}
isGroup={!!conversation?.isGroup}
isFrame={!!framesStore[item.content.toLowerCase().trim()]}
hasFrames={messageHasFrames(item.id, messageFramesMap)}
/>
);
},
[colorScheme, xmtpAddress, conversation?.isGroup, framesStore]
[colorScheme, xmtpAddress, conversation?.isGroup, messageFramesMap]
);
};

Expand Down Expand Up @@ -383,7 +383,9 @@ export function Chat() {
styles.inChatRecommendations,
]);

const { frames: framesStore } = useFramesStore(useSelect(["frames"]));
const { messageFramesMap, frames: framesStore } = useFramesStore(
useSelect(["messageFramesMap", "frames"])
);

const showPlaceholder = useIsShowingPlaceholder({
messages: listArray,
Expand All @@ -394,7 +396,7 @@ export function Chat() {
const renderItem = useRenderItem({
xmtpAddress,
conversation,
framesStore,
messageFramesMap,
colorScheme,
});

Expand Down Expand Up @@ -539,7 +541,9 @@ export function ChatPreview() {
]
);

const { frames: framesStore } = useFramesStore(useSelect(["frames"]));
const { frames: framesStore, messageFramesMap } = useFramesStore(
useSelect(["frames", "messageFramesMap"])
);

const showPlaceholder = useIsShowingPlaceholder({
messages: listArray,
Expand All @@ -550,7 +554,7 @@ export function ChatPreview() {
const renderItem = useRenderItem({
xmtpAddress,
conversation,
framesStore,
messageFramesMap,
colorScheme,
});

Expand Down
46 changes: 19 additions & 27 deletions components/Chat/Frame/FramesPreviews.tsx
Original file line number Diff line number Diff line change
@@ -1,58 +1,50 @@
import { useSelect } from "@data/store/storeHelpers";
import { useConversationContext } from "@utils/conversation";
import { useCallback, useRef, useState } from "react";
import { useCallback, useEffect, useRef } from "react";
import { View } from "react-native";
import { useShallow } from "zustand/react/shallow";

import FramePreview from "./FramePreview";
import { useCurrentAccount } from "../../../data/store/accountsStore";
import { useFramesStore } from "../../../data/store/framesStore";
import {
FrameWithType,
FramesForMessage,
fetchFramesForMessage,
} from "../../../utils/frames";
import { FramesForMessage, fetchFramesForMessage } from "../../../utils/frames";
import { MessageToDisplay } from "../Message/Message";

type Props = {
message: MessageToDisplay;
};

export default function FramesPreviews({ message }: Props) {
export function FramesPreviews({ message }: Props) {
const messageId = useRef<string | undefined>(undefined);
const tagsFetchedOnceForMessage = useConversationContext(
"tagsFetchedOnceForMessage"
);
const account = useCurrentAccount() as string;
const [framesForMessage, setFramesForMessage] = useState<{
[messageId: string]: FrameWithType[];
}>({
[message.id]: useFramesStore
.getState()
.getFramesForURLs(message.converseMetadata?.frames || []),
});
const framesToDisplay = useFramesStore(
useShallow((s) => s.messageFramesMap[message.id] ?? [])
);
const { setMessageFramesMap } = useFramesStore(
useSelect(["setMessageFramesMap"])
);

const fetchTagsIfNeeded = useCallback(() => {
if (!tagsFetchedOnceForMessage.current[message.id]) {
tagsFetchedOnceForMessage.current[message.id] = true;
fetchFramesForMessage(account, message).then(
(frames: FramesForMessage) => {
setFramesForMessage({ [frames.messageId]: frames.frames });
setMessageFramesMap(frames.messageId, frames.frames);
}
);
}
}, [account, message, tagsFetchedOnceForMessage]);
}, [account, message, tagsFetchedOnceForMessage, setMessageFramesMap]);

// Components are recycled, let's fix when stuff changes
if (message.id !== messageId.current) {
messageId.current = message.id;
fetchTagsIfNeeded();
setFramesForMessage({
[message.id]: useFramesStore
.getState()
.getFramesForURLs(message.converseMetadata?.frames || []),
});
}

const framesToDisplay = framesForMessage[message.id] || [];
useEffect(() => {
if (message.id !== messageId.current) {
messageId.current = message.id;
fetchTagsIfNeeded();
}
}, [message.id, fetchTagsIfNeeded]);

return (
<View>
Expand Down
81 changes: 45 additions & 36 deletions components/Chat/Message/Message.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useFramesStore } from "@data/store/framesStore";
import {
inversePrimaryColor,
messageInnerBubbleColor,
Expand Down Expand Up @@ -26,6 +27,7 @@ import Animated, {
useAnimatedStyle,
withTiming,
} from "react-native-reanimated";
import { useShallow } from "zustand/react/shallow";

import ChatMessageActions from "./MessageActions";
import ChatMessageReactions from "./MessageReactions";
Expand Down Expand Up @@ -64,7 +66,7 @@ import ClickableText from "../../ClickableText";
import ActionButton from "../ActionButton";
import AttachmentMessagePreview from "../Attachment/AttachmentMessagePreview";
import ChatGroupUpdatedMessage from "../ChatGroupUpdatedMessage";
import FramesPreviews from "../Frame/FramesPreviews";
import { FramesPreviews } from "../Frame/FramesPreviews";
import ChatInputReplyBubble from "../Input/InputReplyBubble";
import TransactionPreview from "../Transaction/TransactionPreview";

Expand All @@ -84,7 +86,7 @@ type Props = {
message: MessageToDisplay;
colorScheme: ColorSchemeName;
isGroup: boolean;
isFrame: boolean;
hasFrames: boolean;
};

// On iOS, the native context menu view handles the long press, but could potentially trigger the onPress event
Expand Down Expand Up @@ -151,7 +153,7 @@ const ChatMessage = ({
message,
colorScheme,
isGroup,
isFrame,
hasFrames,
}: Props) => {
const styles = useStyles();

Expand All @@ -163,6 +165,10 @@ const ChatMessage = ({
() => getLocalizedTime(message.sent),
[message.sent]
);
// The content is completely a frame so a larger full width frame will be shown
const isFrame = useFramesStore(
useShallow((s) => !!s.frames[message.content.toLowerCase().trim()])
);

// Reanimated shared values for time and date-time animations
const timeHeight = useSharedValue(0);
Expand Down Expand Up @@ -325,6 +331,37 @@ const ChatMessage = ({

const swipeableRef = useRef<Swipeable | null>(null);

const renderLeftActions = useCallback(
(
progressAnimatedValue: RNAnimated.AnimatedInterpolation<string | number>
) => {
return (
<RNAnimated.View
style={{
opacity: progressAnimatedValue.interpolate({
inputRange: [0, 0.7, 1],
outputRange: [0, 0, 1],
}),
height: "100%",
justifyContent: "center",
transform: [
{
translateX: progressAnimatedValue.interpolate({
inputRange: [0, 0.8, 1],
outputRange: [0, 0, 8],
extrapolate: "clamp",
}),
},
],
}}
>
<ActionButton picto="arrowshape.turn.up.left" />
</RNAnimated.View>
);
},
[]
);

return (
<View
style={[
Expand Down Expand Up @@ -358,35 +395,7 @@ const ChatMessage = ({
overshootFriction={1.5}
containerStyle={styles.messageSwipeable}
childrenContainerStyle={styles.messageSwipeableChildren}
renderLeftActions={(
progressAnimatedValue: RNAnimated.AnimatedInterpolation<
string | number
>
) => {
return (
<RNAnimated.View
style={{
opacity: progressAnimatedValue.interpolate({
inputRange: [0, 0.7, 1],
outputRange: [0, 0, 1],
}),
height: "100%",
justifyContent: "center",
transform: [
{
translateX: progressAnimatedValue.interpolate({
inputRange: [0, 0.8, 1],
outputRange: [0, 0, 8],
extrapolate: "clamp",
}),
},
],
}}
>
<ActionButton picto="arrowshape.turn.up.left" />
</RNAnimated.View>
);
}}
renderLeftActions={renderLeftActions}
leftThreshold={10000} // Never trigger opening
onSwipeableWillClose={() => {
const translation = swipeableRef.current?.state.rowTranslation;
Expand Down Expand Up @@ -543,7 +552,7 @@ type RenderedChatMessage = {
message: MessageToDisplay;
colorScheme: ColorSchemeName;
isGroup: boolean;
isFrame: boolean;
hasFrames: boolean;
};

const renderedMessages = new LimitedMap<string, RenderedChatMessage>(50);
Expand All @@ -567,7 +576,7 @@ export default function CachedChatMessage({
message,
colorScheme,
isGroup,
isFrame = false,
hasFrames = false,
}: Props) {
const alreadyRenderedMessage = renderedMessages.get(
`${account}-${message.id}`
Expand All @@ -584,14 +593,14 @@ export default function CachedChatMessage({
message,
colorScheme,
isGroup,
isFrame,
hasFrames,
});
renderedMessages.set(`${account}-${message.id}`, {
message,
renderedMessage,
colorScheme,
isGroup,
isFrame,
hasFrames,
});
return renderedMessage;
} else {
Expand Down
35 changes: 32 additions & 3 deletions data/store/framesStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,38 @@ type FramesStoreType = {
frames: {
[frameUrl: string]: FrameWithType;
};
setFrames: (framesToSet: { [frameUrl: string]: FrameWithType }) => void;
messageFramesMap: {
[messageId: string]: FrameWithType[];
};
setFrames: (
messageId: string,
framesToSet: { [frameUrl: string]: FrameWithType }
) => void;
getFramesForURLs: (urls: string[]) => FrameWithType[];
setMessageFramesMap: (messageId: string, framesUrls: FrameWithType[]) => void;
};

export const useFramesStore = create<FramesStoreType>()(
persist(
(set, get) => ({
frames: {},
setFrames: (framesToSet: { [frameUrl: string]: FrameWithType }) =>
messageFramesMap: {},
setFrames: (
messageId: string,
framesToSet: { [frameUrl: string]: FrameWithType }
) =>
set((state) => {
const existingFrames = pick(state.frames, Object.keys(framesToSet));

if (isDeepEqual(existingFrames, framesToSet)) return {};
return { frames: { ...state.frames, ...framesToSet } };
return {
...state,
frames: { ...state.frames, ...framesToSet },
messageFramesMap: {
...state.messageFramesMap,
[messageId]: Object.values(framesToSet),
},
};
}),
getFramesForURLs: (urls: string[]) => {
const framesToReturn: FrameWithType[] = [];
Expand All @@ -35,6 +54,16 @@ export const useFramesStore = create<FramesStoreType>()(
});
return framesToReturn;
},
setMessageFramesMap: (messageId: string, frames: FrameWithType[]) =>
set((state) => {
return {
...state,
messageFramesMap: {
...state.messageFramesMap,
[messageId]: frames,
},
};
}),
}),
{
name: `store-frames`,
Expand Down
11 changes: 10 additions & 1 deletion utils/frames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ export const fetchFramesForMessage = async (
frames: fetchedFrames.map((f) => f.url),
};
// Save frame to store
useFramesStore.getState().setFrames(framesToSave);
useFramesStore.getState().setFrames(message.id, framesToSave);
// Then update message to reflect change
saveMessageMetadata(account, message, messageMetadataToSave);

Expand Down Expand Up @@ -282,3 +282,12 @@ export const isFrameMessage = (
!!framesStore[message.converseMetadata.frames[0].toLowerCase().trim()]
);
};

export const messageHasFrames = (
messageId: string,
messageFramesMap: {
[messageId: string]: FrameWithType[];
}
) => {
return (messageFramesMap[messageId]?.length ?? 0) > 0;
};

0 comments on commit 6b95e36

Please sign in to comment.