Skip to content

Commit b99320a

Browse files
authored
regression: Touchables not working on old messages in some devices (RocketChat#7039)
1 parent dbba268 commit b99320a

File tree

1 file changed

+13
-146
lines changed

1 file changed

+13
-146
lines changed

app/views/RoomView/List/components/InvertedScrollView.tsx

Lines changed: 13 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -1,153 +1,20 @@
1-
import React, { forwardRef, useRef, useLayoutEffect } from 'react';
2-
import {
3-
findNodeHandle,
4-
requireNativeComponent,
5-
StyleSheet,
6-
UIManager,
7-
type StyleProp,
8-
type ViewStyle,
9-
type LayoutChangeEvent,
10-
type ScrollViewProps,
11-
type ViewProps
12-
} from 'react-native';
1+
import React, { forwardRef } from 'react';
2+
import { ScrollView, requireNativeComponent, type ScrollViewProps, type ViewProps } from 'react-native';
133

14-
const COMMAND_SCROLL_TO = 1;
15-
const COMMAND_SCROLL_TO_END = 2;
16-
const COMMAND_FLASH_SCROLL_INDICATORS = 3;
4+
const NativeInvertedScrollContentView = requireNativeComponent<ViewProps>('InvertedScrollContentView');
175

18-
const styles = StyleSheet.create({
19-
baseVertical: {
20-
flexGrow: 1,
21-
flexShrink: 1,
22-
flexDirection: 'column',
23-
overflow: 'scroll'
24-
},
25-
baseHorizontal: {
26-
flexGrow: 1,
27-
flexShrink: 1,
28-
flexDirection: 'row',
29-
overflow: 'scroll'
30-
}
31-
});
32-
33-
type ScrollViewPropsWithRef = ScrollViewProps & React.RefAttributes<NativeScrollInstance | null>;
34-
type NativeScrollInstance = React.ComponentRef<NonNullable<typeof NativeInvertedScrollView>>;
35-
interface IScrollableMethods {
36-
scrollTo(options?: { x?: number; y?: number; animated?: boolean }): void;
37-
scrollToEnd(options?: { animated?: boolean }): void;
38-
flashScrollIndicators(): void;
39-
getScrollRef(): NativeScrollInstance | null;
40-
setNativeProps(props: object): void;
41-
}
42-
43-
export type InvertedScrollViewRef = NativeScrollInstance & IScrollableMethods;
44-
45-
const NativeInvertedScrollView = requireNativeComponent<ScrollViewProps>('InvertedScrollView');
46-
47-
const NativeInvertedScrollContentView = requireNativeComponent<ViewProps & { removeClippedSubviews?: boolean }>(
48-
'InvertedScrollContentView'
49-
);
50-
51-
const InvertedScrollView = forwardRef<InvertedScrollViewRef, ScrollViewProps>((props, externalRef) => {
52-
const internalRef = useRef<NativeScrollInstance | null>(null);
53-
54-
useLayoutEffect(() => {
55-
const node = internalRef.current as any;
56-
57-
if (node) {
58-
// 1. Implementation of scrollTo
59-
node.scrollTo = (options?: { x?: number; y?: number; animated?: boolean }) => {
60-
const tag = findNodeHandle(node);
61-
if (tag != null) {
62-
const x = options?.x || 0;
63-
const y = options?.y || 0;
64-
const animated = options?.animated !== false;
65-
UIManager.dispatchViewManagerCommand(tag, COMMAND_SCROLL_TO, [x, y, animated]);
66-
}
67-
};
68-
69-
// 2. Implementation of scrollToEnd
70-
node.scrollToEnd = (options?: { animated?: boolean }) => {
71-
const tag = findNodeHandle(node);
72-
if (tag != null) {
73-
const animated = options?.animated !== false;
74-
UIManager.dispatchViewManagerCommand(tag, COMMAND_SCROLL_TO_END, [animated]);
75-
}
76-
};
77-
78-
// 3. Implementation of flashScrollIndicators
79-
node.flashScrollIndicators = () => {
80-
const tag = findNodeHandle(node as any);
81-
if (tag !== null) {
82-
UIManager.dispatchViewManagerCommand(tag, COMMAND_FLASH_SCROLL_INDICATORS, []);
83-
}
84-
};
85-
86-
node.getScrollRef = () => node;
87-
const originalSetNativeProps = (node as any).setNativeProps;
88-
if (typeof originalSetNativeProps !== 'function') {
89-
node.setNativeProps = (_nativeProps: object) => {};
90-
}
91-
}
92-
}, []);
93-
94-
// Callback Ref to handle merging internal and external refs
95-
const setRef = (node: NativeScrollInstance | null) => {
96-
internalRef.current = node;
97-
98-
if (typeof externalRef === 'function') {
99-
externalRef(node as InvertedScrollViewRef);
100-
} else if (externalRef) {
101-
(externalRef as React.MutableRefObject<NativeScrollInstance | null>).current = node;
102-
}
103-
};
104-
105-
const {
106-
children,
107-
contentContainerStyle,
108-
onContentSizeChange,
109-
removeClippedSubviews,
110-
maintainVisibleContentPosition,
111-
snapToAlignment,
112-
stickyHeaderIndices,
113-
...rest
114-
} = props;
115-
116-
const preserveChildren = maintainVisibleContentPosition != null || snapToAlignment != null;
117-
const hasStickyHeaders = Array.isArray(stickyHeaderIndices) && stickyHeaderIndices.length > 0;
118-
119-
const contentContainerStyleArray = [props.horizontal ? { flexDirection: 'row' as const } : null, contentContainerStyle];
120-
121-
const contentSizeChangeProps =
122-
onContentSizeChange == null
123-
? undefined
124-
: {
125-
onLayout: (e: LayoutChangeEvent) => {
126-
const { width, height } = e.nativeEvent.layout;
127-
onContentSizeChange(width, height);
128-
}
129-
};
130-
131-
const horizontal = !!props.horizontal;
132-
const baseStyle = horizontal ? styles.baseHorizontal : styles.baseVertical;
133-
const { style, ...restWithoutStyle } = rest;
134-
135-
if (!NativeInvertedScrollView || !NativeInvertedScrollContentView) {
136-
return null;
137-
}
138-
const ScrollView = NativeInvertedScrollView as React.ComponentType<ScrollViewPropsWithRef>;
139-
const ContentView = NativeInvertedScrollContentView as React.ComponentType<ViewProps & { removeClippedSubviews?: boolean }>;
6+
/**
7+
* Android-only scroll component that wraps the standard ScrollView but uses a native content view
8+
* that reverses accessibility traversal order. This fixes TalkBack reading inverted FlatList items
9+
* in the wrong order, while preserving all ScrollView JS-side behavior (responder handling,
10+
* momentum events, touch coordination).
11+
*/
12+
const InvertedScrollView = forwardRef<ScrollView, ScrollViewProps>((props, ref) => {
13+
const { children, ...rest } = props;
14014

14115
return (
142-
<ScrollView ref={setRef} {...restWithoutStyle} style={StyleSheet.compose(baseStyle, style)} horizontal={horizontal}>
143-
<ContentView
144-
{...contentSizeChangeProps}
145-
removeClippedSubviews={hasStickyHeaders ? false : removeClippedSubviews}
146-
collapsable={false}
147-
collapsableChildren={!preserveChildren}
148-
style={contentContainerStyleArray as StyleProp<ViewStyle>}>
149-
{children}
150-
</ContentView>
16+
<ScrollView ref={ref} {...rest}>
17+
<NativeInvertedScrollContentView collapsable={false}>{children}</NativeInvertedScrollContentView>
15118
</ScrollView>
15219
);
15320
});

0 commit comments

Comments
 (0)