From 59e4a047fd3132630a32a573110be03c58ff0b3c Mon Sep 17 00:00:00 2001 From: OtavioStasiak Date: Thu, 26 Feb 2026 12:25:16 -0300 Subject: [PATCH 01/10] fix: inverted navigation --- .../reactnative/scroll/InvertedScrollContentView.java | 6 ++++++ .../rocket/reactnative/scroll/InvertedScrollView.java | 10 ++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollContentView.java b/android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollContentView.java index a4acb0c1e13..82880ea329e 100644 --- a/android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollContentView.java +++ b/android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollContentView.java @@ -20,4 +20,10 @@ public void addChildrenForAccessibility(ArrayList outChildren) { super.addChildrenForAccessibility(outChildren); Collections.reverse(outChildren); } + + @Override + public void addFocusables(ArrayList views, int direction, int focusableMode) { + super.addFocusables(views, direction, focusableMode); + Collections.reverse(views); + } } diff --git a/android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollView.java b/android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollView.java index def585a7511..d46eb055f73 100644 --- a/android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollView.java +++ b/android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollView.java @@ -18,10 +18,8 @@ public InvertedScrollView(ReactContext context) { super(context); } - // Set whether this ScrollView is used for an inverted virtualized list. When true, we reverse the // accessibility traversal order to match the visual order. - public void setIsInvertedVirtualizedList(boolean isInverted) { mIsInvertedVirtualizedList = isInverted; } @@ -33,4 +31,12 @@ public void addChildrenForAccessibility(ArrayList outChildren) { Collections.reverse(outChildren); } } + + @Override + public void addFocusables(ArrayList views, int direction, int focusableMode) { + super.addFocusables(views, direction, focusableMode); + if (mIsInvertedVirtualizedList) { + Collections.reverse(views); + } + } } From 4eb8bf275a20bfcc66e78516fc838a289f525fe8 Mon Sep 17 00:00:00 2001 From: OtavioStasiak Date: Thu, 26 Feb 2026 18:22:19 -0300 Subject: [PATCH 02/10] fix: just invert list content --- .../scroll/InvertedScrollContentView.java | 14 ++++++++++++-- .../scroll/InvertedScrollContentViewManager.java | 6 ++++++ .../reactnative/scroll/InvertedScrollView.java | 8 -------- .../List/components/InvertedScrollView.tsx | 11 +++++++---- 4 files changed, 25 insertions(+), 14 deletions(-) diff --git a/android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollContentView.java b/android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollContentView.java index 82880ea329e..b983498f10d 100644 --- a/android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollContentView.java +++ b/android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollContentView.java @@ -11,19 +11,29 @@ */ public class InvertedScrollContentView extends ReactViewGroup { + private boolean mIsInvertedContent = false; + public InvertedScrollContentView(android.content.Context context) { super(context); } + public void setIsInvertedContent(boolean isInverted) { + mIsInvertedContent = isInverted; + } + @Override public void addChildrenForAccessibility(ArrayList outChildren) { super.addChildrenForAccessibility(outChildren); - Collections.reverse(outChildren); + if (mIsInvertedContent) { + Collections.reverse(outChildren); + } } @Override public void addFocusables(ArrayList views, int direction, int focusableMode) { super.addFocusables(views, direction, focusableMode); - Collections.reverse(views); + if (mIsInvertedContent) { + Collections.reverse(views); + } } } diff --git a/android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollContentViewManager.java b/android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollContentViewManager.java index d30f9fc84c2..095c2463424 100644 --- a/android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollContentViewManager.java +++ b/android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollContentViewManager.java @@ -2,6 +2,7 @@ import com.facebook.react.module.annotations.ReactModule; import com.facebook.react.uimanager.ThemedReactContext; +import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.views.view.ReactViewManager; /** @@ -22,4 +23,9 @@ public String getName() { public InvertedScrollContentView createViewInstance(ThemedReactContext context) { return new InvertedScrollContentView(context); } + + @ReactProp(name = "isInvertedContent") + public void setIsInvertedContent(InvertedScrollContentView view, boolean isInverted) { + view.setIsInvertedContent(isInverted); + } } diff --git a/android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollView.java b/android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollView.java index d46eb055f73..c01ce3d28e5 100644 --- a/android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollView.java +++ b/android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollView.java @@ -31,12 +31,4 @@ public void addChildrenForAccessibility(ArrayList outChildren) { Collections.reverse(outChildren); } } - - @Override - public void addFocusables(ArrayList views, int direction, int focusableMode) { - super.addFocusables(views, direction, focusableMode); - if (mIsInvertedVirtualizedList) { - Collections.reverse(views); - } - } } diff --git a/app/views/RoomView/List/components/InvertedScrollView.tsx b/app/views/RoomView/List/components/InvertedScrollView.tsx index c160fbcc42a..0ebecc78b91 100644 --- a/app/views/RoomView/List/components/InvertedScrollView.tsx +++ b/app/views/RoomView/List/components/InvertedScrollView.tsx @@ -44,9 +44,9 @@ export type InvertedScrollViewRef = NativeScrollInstance & IScrollableMethods; const NativeInvertedScrollView = requireNativeComponent('InvertedScrollView'); -const NativeInvertedScrollContentView = requireNativeComponent( - 'InvertedScrollContentView' -); +const NativeInvertedScrollContentView = requireNativeComponent< + ViewProps & { removeClippedSubviews?: boolean; isInvertedContent?: boolean } +>('InvertedScrollContentView'); const InvertedScrollView = forwardRef((props, externalRef) => { const internalRef = useRef(null); @@ -136,13 +136,16 @@ const InvertedScrollView = forwardRef((p return null; } const ScrollView = NativeInvertedScrollView as React.ComponentType; - const ContentView = NativeInvertedScrollContentView as React.ComponentType; + const ContentView = NativeInvertedScrollContentView as React.ComponentType< + ViewProps & { removeClippedSubviews?: boolean; isInvertedContent?: boolean } + >; return ( }> From 9b639da231b951830d20aea4d3524076050b6680 Mon Sep 17 00:00:00 2001 From: OtavioStasiak Date: Thu, 26 Feb 2026 18:27:54 -0300 Subject: [PATCH 03/10] fix: lint --- app/views/RoomView/List/components/InvertedScrollView.tsx | 3 ++- app/views/RoomView/List/components/List.tsx | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/views/RoomView/List/components/InvertedScrollView.tsx b/app/views/RoomView/List/components/InvertedScrollView.tsx index 0ebecc78b91..2e1f66ab4af 100644 --- a/app/views/RoomView/List/components/InvertedScrollView.tsx +++ b/app/views/RoomView/List/components/InvertedScrollView.tsx @@ -31,6 +31,7 @@ const styles = StyleSheet.create({ }); type ScrollViewPropsWithRef = ScrollViewProps & React.RefAttributes; +type InvertedScrollViewProps = ScrollViewProps & { inverted?: boolean }; type NativeScrollInstance = React.ComponentRef>; interface IScrollableMethods { scrollTo(options?: { x?: number; y?: number; animated?: boolean }): void; @@ -48,7 +49,7 @@ const NativeInvertedScrollContentView = requireNativeComponent< ViewProps & { removeClippedSubviews?: boolean; isInvertedContent?: boolean } >('InvertedScrollContentView'); -const InvertedScrollView = forwardRef((props, externalRef) => { +const InvertedScrollView = forwardRef((props, externalRef) => { const internalRef = useRef(null); useLayoutEffect(() => { diff --git a/app/views/RoomView/List/components/List.tsx b/app/views/RoomView/List/components/List.tsx index 7ffe0135587..76b5aa37bba 100644 --- a/app/views/RoomView/List/components/List.tsx +++ b/app/views/RoomView/List/components/List.tsx @@ -44,7 +44,7 @@ const List = ({ listRef, jumpToBottom, ...props }: IListProps) => { contentContainerStyle={styles.contentContainer} style={styles.list} inverted - renderScrollComponent={isIOS ? undefined : props => } + renderScrollComponent={isIOS ? undefined : props => } removeClippedSubviews={isIOS} initialNumToRender={7} onEndReachedThreshold={0.5} From cf4cb85a74d98d479abf2ccc5b943d0aa0843867 Mon Sep 17 00:00:00 2001 From: OtavioStasiak Date: Fri, 27 Feb 2026 16:14:43 -0300 Subject: [PATCH 04/10] test: inverted just in the inverted --- app/views/RoomView/List/components/InvertedScrollView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/RoomView/List/components/InvertedScrollView.tsx b/app/views/RoomView/List/components/InvertedScrollView.tsx index 2e1f66ab4af..89bc255ebc3 100644 --- a/app/views/RoomView/List/components/InvertedScrollView.tsx +++ b/app/views/RoomView/List/components/InvertedScrollView.tsx @@ -140,7 +140,7 @@ const InvertedScrollView = forwardRef; - + console.log('props.inverted', props.inverted); return ( Date: Fri, 27 Feb 2026 17:21:32 -0300 Subject: [PATCH 05/10] fix: code --- .../java/chat/rocket/reactnative/scroll/InvertedScrollView.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollView.java b/android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollView.java index c01ce3d28e5..d00b71fd60f 100644 --- a/android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollView.java +++ b/android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollView.java @@ -18,8 +18,10 @@ public InvertedScrollView(ReactContext context) { super(context); } + // Set whether this ScrollView is used for an inverted virtualized list. When true, we reverse the // accessibility traversal order to match the visual order. + public void setIsInvertedVirtualizedList(boolean isInverted) { mIsInvertedVirtualizedList = isInverted; } From a33f49d98d7a7ccd11ede2c116c902237ed56e32 Mon Sep 17 00:00:00 2001 From: OtavioStasiak Date: Fri, 27 Feb 2026 17:29:17 -0300 Subject: [PATCH 06/10] fix: invert just the flatlist content --- .../scroll/InvertedScrollContentView.java | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollContentView.java b/android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollContentView.java index b983498f10d..9543c1b09ab 100644 --- a/android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollContentView.java +++ b/android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollContentView.java @@ -33,7 +33,23 @@ public void addChildrenForAccessibility(ArrayList outChildren) { public void addFocusables(ArrayList views, int direction, int focusableMode) { super.addFocusables(views, direction, focusableMode); if (mIsInvertedContent) { - Collections.reverse(views); + // Find indices of focusables that are children of this view + ArrayList childIndices = new ArrayList<>(); + for (int i = 0; i < views.size(); i++) { + View v = views.get(i); + if (v.getParent() == this) { + childIndices.add(i); + } + } + // Reverse only the sublist of children focusables + int n = childIndices.size(); + for (int i = 0; i < n / 2; i++) { + int idx1 = childIndices.get(i); + int idx2 = childIndices.get(n - 1 - i); + View temp = views.get(idx1); + views.set(idx1, views.get(idx2)); + views.set(idx2, temp); + } } } } From 4e86354ce83bafd8ff2134f3496a15c1e77cbe1c Mon Sep 17 00:00:00 2001 From: OtavioStasiak Date: Mon, 2 Mar 2026 16:25:29 -0300 Subject: [PATCH 07/10] fix: switch tab --- .../scroll/InvertedScrollView.java | 26 ++++++++++++++++++- app/views/RoomView/List/components/List.tsx | 4 +-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollView.java b/android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollView.java index d00b71fd60f..adeb0396504 100644 --- a/android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollView.java +++ b/android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollView.java @@ -8,7 +8,8 @@ // When a FlatList is inverted (inverted={true}), React Native uses scaleY: -1 transform which // visually inverts the list but Android still reports children in array order. This view overrides -// addChildrenForAccessibility to reverse the order so TalkBack matches the visual order. +// addChildrenForAccessibility to reverse the order so TalkBack matches the visual order, and also +// adjusts keyboard/D-pad focus navigation to behave like a non-inverted list. public class InvertedScrollView extends ReactScrollView { @@ -26,6 +27,29 @@ public void setIsInvertedVirtualizedList(boolean isInverted) { mIsInvertedVirtualizedList = isInverted; } + @Override + public View focusSearch(View focused, int direction) { + if (mIsInvertedVirtualizedList) { + switch (direction) { + case View.FOCUS_DOWN: + direction = View.FOCUS_UP; + break; + case View.FOCUS_UP: + direction = View.FOCUS_DOWN; + break; + case View.FOCUS_FORWARD: + direction = View.FOCUS_BACKWARD; + break; + case View.FOCUS_BACKWARD: + direction = View.FOCUS_FORWARD; + break; + default: + break; + } + } + return super.focusSearch(focused, direction); + } + @Override public void addChildrenForAccessibility(ArrayList outChildren) { super.addChildrenForAccessibility(outChildren); diff --git a/app/views/RoomView/List/components/List.tsx b/app/views/RoomView/List/components/List.tsx index 76b5aa37bba..a273264dcc6 100644 --- a/app/views/RoomView/List/components/List.tsx +++ b/app/views/RoomView/List/components/List.tsx @@ -43,8 +43,8 @@ const List = ({ listRef, jumpToBottom, ...props }: IListProps) => { keyExtractor={item => item.id} contentContainerStyle={styles.contentContainer} style={styles.list} - inverted - renderScrollComponent={isIOS ? undefined : props => } + inverted={props.inverted || true} + renderScrollComponent={isIOS ? undefined : scrollProps => } removeClippedSubviews={isIOS} initialNumToRender={7} onEndReachedThreshold={0.5} From 32d0b75c44bd1ad9275557aa007f784efe62b700 Mon Sep 17 00:00:00 2001 From: OtavioStasiak Date: Mon, 2 Mar 2026 17:59:49 -0300 Subject: [PATCH 08/10] try to reverse just the flatlist content --- .../scroll/InvertedScrollView.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollView.java b/android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollView.java index adeb0396504..673a72cf79d 100644 --- a/android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollView.java +++ b/android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollView.java @@ -50,6 +50,26 @@ public View focusSearch(View focused, int direction) { return super.focusSearch(focused, direction); } + @Override + public void addFocusables(ArrayList views, int direction, int focusableMode) { + int initialSize = views.size(); + + super.addFocusables(views, direction, focusableMode); + + if (!mIsInvertedVirtualizedList) { + return; + } + + if (direction == View.FOCUS_FORWARD || direction == View.FOCUS_BACKWARD) { + int newSize = views.size(); + int addedCount = newSize - initialSize; + if (addedCount > 1) { + java.util.List subList = views.subList(initialSize, newSize); + Collections.reverse(subList); + } + } + } + @Override public void addChildrenForAccessibility(ArrayList outChildren) { super.addChildrenForAccessibility(outChildren); From 037d5c6dc75d4f3b31e8baba1615976417fb6745 Mon Sep 17 00:00:00 2001 From: OtavioStasiak Date: Wed, 4 Mar 2026 18:11:19 -0300 Subject: [PATCH 09/10] fix: inverted list direction --- app/views/RoomView/List/components/List.tsx | 42 ++++++++++++++++++++- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/app/views/RoomView/List/components/List.tsx b/app/views/RoomView/List/components/List.tsx index a273264dcc6..42dfc77facb 100644 --- a/app/views/RoomView/List/components/List.tsx +++ b/app/views/RoomView/List/components/List.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { StyleSheet, View } from 'react-native'; +import { Platform, StyleSheet, View } from 'react-native'; import Animated, { runOnJS, useAnimatedScrollHandler } from 'react-native-reanimated'; import { isIOS } from '../../../../lib/methods/helpers'; @@ -22,6 +22,42 @@ const styles = StyleSheet.create({ const List = ({ listRef, jumpToBottom, ...props }: IListProps) => { const [visible, setVisible] = useState(false); const { isAutocompleteVisible } = useRoomContext(); + const { data, renderItem, ...flatListProps } = props; + + const renderItemWithFocus: IListProps['renderItem'] = info => { + if (!renderItem) { + return null as any; + } + + if (Platform.OS !== 'android') { + return renderItem(info); + } + + const total = data?.length ?? 0; + const { index } = info; + const itemId = `room-message-${index}`; + + const nextFocusUp = index < total - 1 ? `room-message-${index + 1}` : undefined; + const nextFocusDown = index > 0 ? `room-message-${index - 1}` : undefined; + + return ( + + {renderItem(info)} + + ); + }; + const scrollHandler = useAnimatedScrollHandler({ onScroll: event => { if (event.contentOffset.y > SCROLL_LIMIT) { @@ -36,11 +72,14 @@ const List = ({ listRef, jumpToBottom, ...props }: IListProps) => { {/* @ts-ignore */} item.id} + data={data} + renderItem={renderItemWithFocus} contentContainerStyle={styles.contentContainer} style={styles.list} inverted={props.inverted || true} @@ -52,7 +91,6 @@ const List = ({ listRef, jumpToBottom, ...props }: IListProps) => { windowSize={10} scrollEventThrottle={16} onScroll={scrollHandler} - {...props} {...scrollPersistTaps} /> From 8886ec327a2753cce7a0c411c042338b9ea2c79e Mon Sep 17 00:00:00 2001 From: OtavioStasiak Date: Wed, 4 Mar 2026 21:14:03 +0000 Subject: [PATCH 10/10] chore: format code and fix lint issues --- app/views/RoomView/List/components/List.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/views/RoomView/List/components/List.tsx b/app/views/RoomView/List/components/List.tsx index 42dfc77facb..64ee74a8e79 100644 --- a/app/views/RoomView/List/components/List.tsx +++ b/app/views/RoomView/List/components/List.tsx @@ -50,9 +50,8 @@ const List = ({ listRef, jumpToBottom, ...props }: IListProps) => { nextFocusUp, // @ts-ignore Android-only props not in ViewProps types nextFocusDown - } - : null)} - > + } + : null)}> {renderItem(info)} );