Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,45 @@
*/
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<View> outChildren) {
super.addChildrenForAccessibility(outChildren);
Collections.reverse(outChildren);
if (mIsInvertedContent) {
Collections.reverse(outChildren);
}
}

@Override
public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
super.addFocusables(views, direction, focusableMode);
if (mIsInvertedContent) {
// Find indices of focusables that are children of this view
ArrayList<Integer> 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);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -21,11 +22,54 @@ public InvertedScrollView(ReactContext 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;
}

@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 addFocusables(ArrayList<View> 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<View> subList = views.subList(initialSize, newSize);
Collections.reverse(subList);
}
}
}

@Override
public void addChildrenForAccessibility(ArrayList<View> outChildren) {
super.addChildrenForAccessibility(outChildren);
Expand Down
16 changes: 10 additions & 6 deletions app/views/RoomView/List/components/InvertedScrollView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const styles = StyleSheet.create({
});

type ScrollViewPropsWithRef = ScrollViewProps & React.RefAttributes<NativeScrollInstance | null>;
type InvertedScrollViewProps = ScrollViewProps & { inverted?: boolean };
type NativeScrollInstance = React.ComponentRef<NonNullable<typeof NativeInvertedScrollView>>;
interface IScrollableMethods {
scrollTo(options?: { x?: number; y?: number; animated?: boolean }): void;
Expand All @@ -44,11 +45,11 @@ export type InvertedScrollViewRef = NativeScrollInstance & IScrollableMethods;

const NativeInvertedScrollView = requireNativeComponent<ScrollViewProps>('InvertedScrollView');

const NativeInvertedScrollContentView = requireNativeComponent<ViewProps & { removeClippedSubviews?: boolean }>(
'InvertedScrollContentView'
);
const NativeInvertedScrollContentView = requireNativeComponent<
ViewProps & { removeClippedSubviews?: boolean; isInvertedContent?: boolean }
>('InvertedScrollContentView');

const InvertedScrollView = forwardRef<InvertedScrollViewRef, ScrollViewProps>((props, externalRef) => {
const InvertedScrollView = forwardRef<InvertedScrollViewRef, InvertedScrollViewProps>((props, externalRef) => {
const internalRef = useRef<NativeScrollInstance | null>(null);

useLayoutEffect(() => {
Expand Down Expand Up @@ -136,13 +137,16 @@ const InvertedScrollView = forwardRef<InvertedScrollViewRef, ScrollViewProps>((p
return null;
}
const ScrollView = NativeInvertedScrollView as React.ComponentType<ScrollViewPropsWithRef>;
const ContentView = NativeInvertedScrollContentView as React.ComponentType<ViewProps & { removeClippedSubviews?: boolean }>;

const ContentView = NativeInvertedScrollContentView as React.ComponentType<
ViewProps & { removeClippedSubviews?: boolean; isInvertedContent?: boolean }
>;
console.log('props.inverted', props.inverted);
return (
<ScrollView ref={setRef} {...restWithoutStyle} style={StyleSheet.compose(baseStyle, style)} horizontal={horizontal}>
<ContentView
{...contentSizeChangeProps}
removeClippedSubviews={hasStickyHeaders ? false : removeClippedSubviews}
isInvertedContent={!!props.inverted}
collapsable={false}
collapsableChildren={!preserveChildren}
style={contentContainerStyleArray as StyleProp<ViewStyle>}>
Expand Down
43 changes: 39 additions & 4 deletions app/views/RoomView/List/components/List.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -22,6 +22,39 @@ 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 nextFocusUp = index < total - 1 ? index + 1 : undefined;
const nextFocusDown = index > 0 ? index + 1 : undefined;
return (
<View
nativeID={`${index}`}
focusable
{...(Platform.OS === 'android'
? {
// @ts-ignore Android-only props not in ViewProps types; accepts native component refs
nextFocusUp,
// @ts-ignore Android-only props not in ViewProps types; accepts native component refs
nextFocusDown
}
: null)}>
{renderItem(info)}
</View>
);
};

const scrollHandler = useAnimatedScrollHandler({
onScroll: event => {
if (event.contentOffset.y > SCROLL_LIMIT) {
Expand All @@ -36,23 +69,25 @@ const List = ({ listRef, jumpToBottom, ...props }: IListProps) => {
<View style={styles.list}>
{/* @ts-ignore */}
<Animated.FlatList
{...flatListProps}
accessibilityElementsHidden={isAutocompleteVisible}
importantForAccessibility={isAutocompleteVisible ? 'no-hide-descendants' : 'yes'}
testID='room-view-messages'
ref={listRef}
keyExtractor={item => item.id}
data={data}
renderItem={renderItemWithFocus}
contentContainerStyle={styles.contentContainer}
style={styles.list}
inverted
renderScrollComponent={isIOS ? undefined : props => <InvertedScrollView {...props} />}
inverted={props.inverted || true}
renderScrollComponent={isIOS ? undefined : scrollProps => <InvertedScrollView {...scrollProps} />}
removeClippedSubviews={isIOS}
initialNumToRender={7}
onEndReachedThreshold={0.5}
maxToRenderPerBatch={5}
windowSize={10}
scrollEventThrottle={16}
onScroll={scrollHandler}
{...props}
{...scrollPersistTaps}
/>
<NavBottomFAB visible={visible} onPress={jumpToBottom} />
Expand Down
Loading