Skip to content

Commit 6cc2fc6

Browse files
authored
fix: ctx menu positioning on resizable viewport (#3411)
## 🎯 Goal This PR addresses an issue with the portaling of our message view towards the `MessageHostOverlay`, where if there are layout changes to the viewport (i.e keyboard/attachment picker closing) the closing animation would animate to a stale position of the message (and then snap back into place as the portal closes). We keep a revision of any corrections we need to do post opening the overlay in order to address this. ## 🛠 Implementation details <!-- Provide a description of the implementation --> ## 🎨 UI Changes <!-- Add relevant screenshots --> <details> <summary>iOS</summary> <table> <thead> <tr> <td>Before</td> <td>After</td> </tr> </thead> <tbody> <tr> <td> <!--<img src="" /> --> </td> <td> <!--<img src="" /> --> </td> </tr> </tbody> </table> </details> <details> <summary>Android</summary> <table> <thead> <tr> <td>Before</td> <td>After</td> </tr> </thead> <tbody> <tr> <td> <!--<img src="" /> --> </td> <td> <!--<img src="" /> --> </td> </tr> </tbody> </table> </details> ## 🧪 Testing <!-- Explain how this change can be tested (or why it can't be tested) --> ## ☑️ Checklist - [ ] I have signed the [Stream CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform) (required) - [ ] PR targets the `develop` branch - [ ] Documentation is updated - [ ] New code is tested in main example apps, including all possible scenarios - [ ] SampleApp iOS and Android - [ ] Expo iOS and Android
1 parent fad1b36 commit 6cc2fc6

4 files changed

Lines changed: 38 additions & 12 deletions

File tree

examples/SampleApp/yarn.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8354,10 +8354,10 @@ stream-chat-react-native-core@8.1.0:
83548354
version "0.0.0"
83558355
uid ""
83568356

8357-
stream-chat@^9.30.1:
8358-
version "9.30.1"
8359-
resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-9.30.1.tgz#86d152e4d0894854370512d17530854541f7990b"
8360-
integrity sha512-8f58tCo3QfgzaNhWHpRQzEfglSPPn4lGRn74FFTr/pn53dMJwtcKDSohV6NTHBrkYWTXYObRnHgh2IhGFUKckw==
8357+
stream-chat@^9.33.0:
8358+
version "9.34.0"
8359+
resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-9.34.0.tgz#e92c3262e1b6fbe92b1b1148286ee152849250dc"
8360+
integrity sha512-b65Z+ufAtygAwT2dCQ8ImgMx01b9zgS1EZ8OK5lRHhSJKYKSsSa1pS3USbbFq6QpuwGZwXM3lovGXLYoWiG84g==
83618361
dependencies:
83628362
"@types/jsonwebtoken" "^9.0.8"
83638363
"@types/ws" "^8.5.14"

package/src/components/MessageList/MessageList.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ import { ThreadContextValue, useThreadContext } from '../../contexts/threadConte
6565

6666
import { useStableCallback } from '../../hooks';
6767
import { useStateStore } from '../../hooks/useStateStore';
68+
import { bumpOverlayLayoutRevision } from '../../state-store';
6869
import { MessageInputHeightState } from '../../state-store/message-input-height-store';
6970
import { primitives } from '../../theme';
7071
import { MessageWrapper } from '../Message/MessageSimple/MessageWrapper';
@@ -1171,7 +1172,13 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => {
11711172
if (additionalFlatListProps?.onLayout) {
11721173
additionalFlatListProps.onLayout(event);
11731174
}
1174-
viewportHeightRef.current = event.nativeEvent.layout.height;
1175+
const nextViewportHeight = event.nativeEvent.layout.height;
1176+
if (viewportHeightRef.current !== nextViewportHeight) {
1177+
const previousViewportHeight = viewportHeightRef.current ?? nextViewportHeight;
1178+
const closeCorrectionDeltaY = nextViewportHeight - previousViewportHeight;
1179+
bumpOverlayLayoutRevision(closeCorrectionDeltaY);
1180+
}
1181+
viewportHeightRef.current = nextViewportHeight;
11751182
});
11761183

11771184
if (!ListComponent) {

package/src/contexts/overlayContext/MessageOverlayHostLayer.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export const MessageOverlayHostLayer = () => {
3131
const messageH = useSharedValue<Rect>(undefined);
3232
const topH = useSharedValue<Rect>(undefined);
3333
const bottomH = useSharedValue<Rect>(undefined);
34+
const closeCorrectionY = useSharedValue(0);
3435

3536
const topInset = insets.top;
3637
// Due to edge-to-edge in combination with various libraries, Android sometimes reports
@@ -50,10 +51,17 @@ export const MessageOverlayHostLayer = () => {
5051
useEffect(
5152
() =>
5253
registerOverlaySharedValueController({
54+
incrementCloseCorrectionY: (deltaY) => {
55+
closeCorrectionY.value += deltaY;
56+
},
57+
resetCloseCorrectionY: () => {
58+
closeCorrectionY.value = 0;
59+
},
5360
reset: () => {
5461
messageH.value = undefined;
5562
topH.value = undefined;
5663
bottomH.value = undefined;
64+
closeCorrectionY.value = 0;
5765
},
5866
setBottomH: (rect) => {
5967
bottomH.value = rect;
@@ -65,7 +73,7 @@ export const MessageOverlayHostLayer = () => {
6573
topH.value = rect;
6674
},
6775
}),
68-
[bottomH, messageH, topH],
76+
[bottomH, closeCorrectionY, messageH, topH],
6977
);
7078

7179
useEffect(() => {
@@ -163,7 +171,7 @@ export const MessageOverlayHostLayer = () => {
163171
});
164172

165173
const topItemTranslateStyle = useAnimatedStyle(() => {
166-
const target = isActive ? (closing ? 0 : shiftY.value) : 0;
174+
const target = isActive ? (closing ? closeCorrectionY.value : shiftY.value) : 0;
167175
return {
168176
transform: [
169177
{ scale: backdrop.value },
@@ -184,7 +192,7 @@ export const MessageOverlayHostLayer = () => {
184192
});
185193

186194
const bottomItemTranslateStyle = useAnimatedStyle(() => {
187-
const target = isActive ? (closing ? 0 : shiftY.value) : 0;
195+
const target = isActive ? (closing ? closeCorrectionY.value : shiftY.value) : 0;
188196
return {
189197
transform: [
190198
{ scale: backdrop.value },
@@ -205,7 +213,7 @@ export const MessageOverlayHostLayer = () => {
205213
});
206214

207215
const hostTranslateStyle = useAnimatedStyle(() => {
208-
const target = isActive ? (closing ? 0 : shiftY.value) : 0;
216+
const target = isActive ? (closing ? closeCorrectionY.value : shiftY.value) : 0;
209217

210218
return {
211219
transform: [

package/src/state-store/message-overlay-store.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ const DefaultState = {
1717
};
1818

1919
type OverlaySharedValueController = {
20+
incrementCloseCorrectionY: (deltaY: number) => void;
21+
resetCloseCorrectionY: () => void;
2022
reset: () => void;
2123
setBottomH: (rect: Rect) => void;
2224
setMessageH: (rect: Rect) => void;
@@ -46,10 +48,19 @@ export const setOverlayBottomH = (bottomH: Rect) => {
4648
sharedValueController?.setBottomH(bottomH);
4749
};
4850

49-
export const openOverlay = (id: string) => overlayStore.partialNext({ closing: false, id });
51+
export const bumpOverlayLayoutRevision = (closeCorrectionDeltaY = 0) => {
52+
sharedValueController?.incrementCloseCorrectionY(closeCorrectionDeltaY);
53+
};
54+
55+
export const openOverlay = (id: string) => {
56+
sharedValueController?.resetCloseCorrectionY();
57+
overlayStore.partialNext({ closing: false, id });
58+
};
5059

5160
export const closeOverlay = () => {
52-
requestAnimationFrame(() => overlayStore.partialNext({ closing: true }));
61+
requestAnimationFrame(() => {
62+
overlayStore.partialNext({ closing: true });
63+
});
5364
};
5465

5566
let actionQueue: Array<() => void | Promise<void>> = [];
@@ -64,8 +75,8 @@ export const scheduleActionOnClose = (action: () => void | Promise<void>) => {
6475
};
6576

6677
export const finalizeCloseOverlay = () => {
67-
sharedValueController?.reset();
6878
overlayStore.partialNext(DefaultState);
79+
sharedValueController?.reset();
6980
};
7081

7182
export const overlayStore = new StateStore<OverlayState>(DefaultState);

0 commit comments

Comments
 (0)