Skip to content

Commit 4fc3c69

Browse files
committed
feat: reimplement portal handling to account for pressability
1 parent 697c90e commit 4fc3c69

File tree

4 files changed

+615
-57
lines changed

4 files changed

+615
-57
lines changed

package/src/components/Message/Message.tsx

Lines changed: 91 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
1-
import React, { useMemo, useRef, useState } from 'react';
1+
import React, { useEffect, useMemo, useRef, useState } from 'react';
22
import {
3+
Alert,
34
findNodeHandle,
45
GestureResponderEvent,
56
Keyboard,
7+
LayoutAnimation,
68
Pressable,
79
StyleProp,
10+
StyleSheet,
811
TouchableWithoutFeedback,
912
UIManager,
1013
View,
1114
ViewStyle,
1215
} from 'react-native';
1316

17+
import Animated, { Easing, FadeIn, FadeOut, useSharedValue } from 'react-native-reanimated';
1418
import { Portal } from 'react-native-teleport';
1519

1620
import type { Attachment, LocalMessage, UserResponse } from 'stream-chat';
@@ -21,8 +25,10 @@ import { useMessageActions } from './hooks/useMessageActions';
2125
import { useMessageDeliveredData } from './hooks/useMessageDeliveryData';
2226
import { useMessageReadData } from './hooks/useMessageReadData';
2327
import { useProcessReactions } from './hooks/useProcessReactions';
28+
import { MessageOverlayBundle } from './MessageOverlayBundle';
2429
import { messageActions as defaultMessageActions } from './utils/messageActions';
2530

31+
import { closeOverlay, openOverlay, useOverlayController } from '../../contexts';
2632
import {
2733
ChannelContextValue,
2834
useChannelContext,
@@ -241,12 +247,12 @@ export type MessagePropsWithContext = Pick<
241247
* each individual Message component.
242248
*/
243249
const MessageWithContext = (props: MessagePropsWithContext) => {
244-
const [messageOverlayVisible, setMessageOverlayVisible] = useState<{
245-
x: number;
246-
y: number;
247-
w: number;
248-
h: number;
249-
} | null>(null);
250+
// const [messageOverlayVisible, setMessageOverlayVisible] = useState<{
251+
// x: number;
252+
// y: number;
253+
// w: number;
254+
// h: number;
255+
// } | null>(null);
250256
const [isErrorInMessage, setIsErrorInMessage] = useState(false);
251257
const [showMessageReactions, setShowMessageReactions] = useState(true);
252258
const [isBounceDialogOpen, setIsBounceDialogOpen] = useState(false);
@@ -326,19 +332,28 @@ const MessageWithContext = (props: MessagePropsWithContext) => {
326332
},
327333
} = useTheme();
328334

335+
const topH = useSharedValue<{ w: number; h: number; x: number; y: number } | undefined>(
336+
undefined,
337+
);
338+
const bottomH = useSharedValue({ w: 0, h: 0, x: 0, y: 0 });
339+
329340
const showMessageOverlay = async (showMessageReactions = false, selectedReaction?: string) => {
330341
await dismissKeyboard();
331-
const layout = await measureInWindow(messageWrapperRef.current);
332-
setShowMessageReactions(showMessageReactions);
333-
setMessageOverlayVisible(layout);
334-
setSelectedReaction(selectedReaction);
342+
try {
343+
const layout = await measureInWindow(messageWrapperRef.current);
344+
setShowMessageReactions(showMessageReactions);
345+
// LayoutAnimation.configureNext(LayoutAnimation.Presets.spring);
346+
openOverlay(message.id, { state: { isMyMessage, rect: layout }, topH, bottomH });
347+
setSelectedReaction(selectedReaction);
348+
} catch (e) {
349+
console.error(e);
350+
}
335351
};
336352

337353
const { setNativeScrollability } = useMessageListItemContext();
338354

339355
const dismissOverlay = () => {
340-
setNativeScrollability(true);
341-
setMessageOverlayVisible(null);
356+
closeOverlay();
342357
};
343358

344359
const actionsEnabled =
@@ -762,6 +777,16 @@ const MessageWithContext = (props: MessagePropsWithContext) => {
762777
videos: attachments.videos,
763778
});
764779

780+
const { state, id, closing } = useOverlayController();
781+
782+
const active = id === message.id;
783+
784+
useEffect(() => {
785+
if (!active && setNativeScrollability) {
786+
setNativeScrollability(true);
787+
}
788+
}, [setNativeScrollability, active]);
789+
765790
if (!(isMessageTypeDeleted || messageContentOrder.length)) {
766791
return null;
767792
}
@@ -770,6 +795,8 @@ const MessageWithContext = (props: MessagePropsWithContext) => {
770795
return <MessageBlocked message={message} />;
771796
}
772797

798+
console.log('ACTIVE: ', active);
799+
773800
return (
774801
<MessageProvider value={messageContext}>
775802
<View
@@ -793,49 +820,56 @@ const MessageWithContext = (props: MessagePropsWithContext) => {
793820
]}
794821
testID='message-wrapper'
795822
>
796-
{messageOverlayVisible ? (
823+
{active ? (
797824
<View
798825
style={{
799-
width: messageOverlayVisible.w,
800-
height: messageOverlayVisible.h,
826+
width: state.rect.w,
827+
height: state.rect.h,
801828
}}
802829
/>
803830
) : null}
804-
<Portal hostName={messageOverlayVisible ? 'overlay' : undefined}>
805-
<View
806-
style={
807-
messageOverlayVisible
808-
? [
809-
{
810-
flex: 1,
811-
justifyContent: 'flex-end',
812-
},
813-
{ backgroundColor: overlay },
814-
]
815-
: null
816-
}
817-
>
818-
{messageOverlayVisible ? (
819-
<TouchableWithoutFeedback onPress={dismissOverlay} style={{ flex: 1 }}>
820-
<View style={{ flex: 1 }} />
821-
</TouchableWithoutFeedback>
822-
) : null}
823-
<View
824-
style={
825-
messageOverlayVisible
826-
? {
827-
position: 'absolute',
828-
top: messageOverlayVisible.y,
829-
...(isMyMessage
830-
? { right: messageOverlayVisible.x }
831-
: { left: messageOverlayVisible.x }),
832-
}
833-
: null
834-
}
831+
<Portal hostName={active && !closing ? 'top-item' : undefined}>
832+
{active && !closing ? (
833+
<Pressable
834+
onLayout={(e) => {
835+
const { x, y, width: w, height: h } = e.nativeEvent.layout;
836+
console.log('BLA', x, y, w, h);
837+
topH.value = { x, y, w, h };
838+
}}
839+
onPress={() => Alert.alert('HIT TOP')}
840+
>
841+
<ReactionList />
842+
</Pressable>
843+
) : null}
844+
</Portal>
845+
<Portal
846+
hostName={active ? 'message-overlay' : undefined}
847+
style={active ? { width: state.rect.w } : null}
848+
>
849+
{/* The message itself: NOT animated; it just rides along as a child */}
850+
<MessageSimple ref={messageWrapperRef} />
851+
852+
{/* Actions below; height measured */}
853+
{active && !closing ? (
854+
<Animated.View
855+
entering={FadeIn.duration(ANIMATED_DURATION).easing(Easing.in(Easing.cubic))}
856+
exiting={FadeOut.duration(ANIMATED_DURATION).easing(Easing.out(Easing.cubic))}
857+
onLayout={(e) => {
858+
const { x, y, width: w, height: h } = e.nativeEvent.layout;
859+
// eslint-disable-next-line sort-keys
860+
bottomH.value = { x, y, w, h };
861+
}}
862+
style={{
863+
position: 'absolute',
864+
top: state.rect.h,
865+
...(isMyMessage ? { right: state.rect.x } : { left: state.rect.x }),
866+
}}
835867
>
836-
<MessageSimple ref={messageWrapperRef} />
837-
</View>
838-
</View>
868+
<Pressable onPress={() => Alert.alert('HIT BOTTOM')}>
869+
<MessageActions />
870+
</Pressable>
871+
</Animated.View>
872+
) : null}
839873
</Portal>
840874
{isBounceDialogOpen ? (
841875
<MessageBounce setIsBounceDialogOpen={setIsBounceDialogOpen} />
@@ -1075,3 +1109,10 @@ export const Message = (props: MessageProps) => {
10751109
/>
10761110
);
10771111
};
1112+
1113+
const ReactionList = () => <View style={{ backgroundColor: 'blue', width: 150, height: 30 }} />;
1114+
const MessageActions = () => (
1115+
<View style={{ backgroundColor: 'purple', width: 150, height: 200 }} />
1116+
);
1117+
1118+
const ANIMATED_DURATION = 250;

0 commit comments

Comments
 (0)