Skip to content

Commit

Permalink
[native] Fix NUX Android problem
Browse files Browse the repository at this point in the history
Summary:
issue: [[https://linear.app/comm/issue/ENG-9472/nux-appears-outside-of-safe-area-on-android | ENG-9472]]
On some Androids, we cannot measure a button that is not visible on the screen. `undefined`s are returned. So we have to wait of the login screen to be dismissed, before we call `measure`.
I wanted to pass the `measure` function itseft through conxtext, but that results in `Cannot read property '_nativeTag' of undefined` error. Not sure what that means, but I don't think passing the ref is much worse.
`measure` asynchronously measures the component and then calls the callback. This is by I need states and an effect, instead of a memo.

`buttonMeasured` flag ensures the tip doesn't jump after the `measure` is done. `measure` returns quickly and I don't think this slight delay is a problem.

Test Plan:
Tested on Android 11 physical device, and iOS 17.4 simulator. Tesdted by updating `NUXHandlerInner` to run the effect on every app reaload.
Checked that on both Android and iOS the NUX tips are displayed in the correct position. Checked thare are no odd visual effects, like jumping (thanks to `buttonMeasured`)

Reviewers: tomek, kamil

Reviewed By: tomek

Subscribers: ashoat

Differential Revision: https://phab.comm.dev/D13697
  • Loading branch information
InkaAlicja committed Oct 14, 2024
1 parent 7617de9 commit e43c6b9
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 99 deletions.
43 changes: 20 additions & 23 deletions native/chat/chat-tab-bar.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ import { MaterialTopTabBar } from '@react-navigation/material-top-tabs';
import invariant from 'invariant';
import * as React from 'react';
import { View } from 'react-native';
import type { MeasureOnSuccessCallback } from 'react-native/Libraries/Renderer/shims/ReactNativeTypes';
import { TabBarItem } from 'react-native-tab-view';

import type { ReactRefSetter } from 'lib/types/react-types.js';

import {
nuxTip,
NUXTipsContext,
Expand All @@ -25,35 +28,29 @@ const ButtonTitleToTip = Object.freeze({
[HomeChatThreadListRouteName]: nuxTip.HOME,
});

const onLayout = () => {};

function TabBarButton(props: TabBarItemProps<Route<>>) {
const tipsContext = React.useContext(NUXTipsContext);
invariant(tipsContext, 'NUXTipsContext should be defined');
const { registerTipButton } = tipsContext;

const viewRef = React.useRef<?React.ElementRef<typeof View>>();
const onLayout = React.useCallback(() => {
const button = viewRef.current;
if (!button) {
return;
}

const tipType = ButtonTitleToTip[props.route.name];
if (!tipType) {
return;
}
button.measure((x, y, width, height, pageX, pageY) => {
tipsContext.registerTipButton(tipType, {
x,
y,
width,
height,
pageX,
pageY,
});
});
}, [props.route.name, tipsContext]);
const registerRef: ReactRefSetter<React.ElementRef<typeof View>> =
React.useCallback(
element => {
const tipType = ButtonTitleToTip[props.route.name];
if (!tipType) {
return;
}
const measure = (callback: MeasureOnSuccessCallback) =>
element?.measure(callback);
registerTipButton(tipType, measure);
},
[props.route.name, registerTipButton],
);

return (
<View ref={viewRef} onLayout={onLayout}>
<View ref={registerRef} onLayout={onLayout}>
<TabBarItem {...props} />
</View>
);
Expand Down
36 changes: 17 additions & 19 deletions native/chat/chat.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@ import { StackView } from '@react-navigation/stack';
import invariant from 'invariant';
import * as React from 'react';
import { Platform, View, useWindowDimensions } from 'react-native';
import type { MeasureOnSuccessCallback } from 'react-native/Libraries/Renderer/shims/ReactNativeTypes';

import MessageStorePruner from 'lib/components/message-store-pruner.react.js';
import ThreadDraftUpdater from 'lib/components/thread-draft-updater.react.js';
import { isLoggedIn } from 'lib/selectors/user-selectors.js';
import { threadSettingsNotificationsCopy } from 'lib/shared/thread-settings-notifications-utils.js';
import { threadIsPending, threadIsSidebar } from 'lib/shared/thread-utils.js';
import type { ReactRefSetter } from 'lib/types/react-types.js';

import BackgroundChatThreadList from './background-chat-thread-list.react.js';
import ChatHeader from './chat-header.react.js';
Expand Down Expand Up @@ -362,6 +364,8 @@ const Chat = createChatNavigator<
ChatNavigationHelpers<ScreenParamList>,
>();

const communityDrawerButtonOnLayout = () => {};

type Props = {
+navigation: TabNavigationProp<'Chat'>,
...
Expand All @@ -375,27 +379,21 @@ export default function ChatComponent(props: Props): React.Node {
draftUpdater = <ThreadDraftUpdater />;
}

const communityDrawerButtonRef =
React.useRef<?React.ElementRef<typeof View>>();

const tipsContext = React.useContext(NUXTipsContext);
invariant(tipsContext, 'NUXTipsContext should be defined');
const { registerTipButton } = tipsContext;

const communityDrawerButtonOnLayout = React.useCallback(() => {
communityDrawerButtonRef.current?.measure(
(x, y, width, height, pageX, pageY) => {
registerTipButton(nuxTip.COMMUNITY_DRAWER, {
x,
y,
width,
height,
pageX,
pageY,
});
},
);
}, [registerTipButton]);
const communityDrawerButtonRegisterRef: ReactRefSetter<
React.ElementRef<typeof View>,
> = React.useCallback(
element => {
const measure = (callback: MeasureOnSuccessCallback) =>
element?.measure(callback);

registerTipButton(nuxTip.COMMUNITY_DRAWER, measure);
},
[registerTipButton],
);

const headerLeftButton = React.useCallback(
(headerProps: StackHeaderLeftButtonProps) => {
Expand All @@ -405,13 +403,13 @@ export default function ChatComponent(props: Props): React.Node {
return (
<View
onLayout={communityDrawerButtonOnLayout}
ref={communityDrawerButtonRef}
ref={communityDrawerButtonRegisterRef}
>
<CommunityDrawerButton navigation={props.navigation} />
</View>
);
},
[communityDrawerButtonOnLayout, props.navigation],
[communityDrawerButtonRegisterRef, props.navigation],
);

const messageEditingContext = React.useContext(MessageEditingContext);
Expand Down
10 changes: 2 additions & 8 deletions native/components/nux-tips-context.react.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// @flow

import * as React from 'react';
import type { MeasureOnSuccessCallback } from 'react-native/Libraries/Renderer/shims/ReactNativeTypes';

import { values } from 'lib/utils/objects.js';

Expand Down Expand Up @@ -61,14 +62,7 @@ function getNUXTipParams(currentTipKey: NUXTip): NUXTipParams {
return nuxTipParams[currentTipKey];
}

type TipProps = {
+x: number,
+y: number,
+width: number,
+height: number,
+pageX: number,
+pageY: number,
};
type TipProps = (callback: MeasureOnSuccessCallback) => void;

export type NUXTipsContextType = {
+registerTipButton: (type: NUXTip, tipProps: ?TipProps) => void,
Expand Down
Loading

0 comments on commit e43c6b9

Please sign in to comment.